mirror of
https://github.com/nlohmann/json.git
synced 2026-09-25 09:20:32 +00:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f7c0f3ea7 | ||
|
|
f290b36ad2 | ||
|
|
5f673b0eae | ||
|
|
e94e164b07 | ||
|
|
4daca40d7b | ||
|
|
7c90ec2323 | ||
|
|
305ca7dadd | ||
|
|
2e91641de2 | ||
|
|
8699de3064 | ||
|
|
5f659c881a | ||
|
|
36c079149a | ||
|
|
770f62dda9 | ||
|
|
bc066c1838 | ||
|
|
918da64657 | ||
|
|
f3768d6868 | ||
|
|
56b3ee566c | ||
|
|
ed513715a8 | ||
|
|
f751547a81 | ||
|
|
06b0452189 | ||
|
|
1054b2097e | ||
|
|
f92024b317 | ||
|
|
0b20b7e622 | ||
|
|
d2c1a6a272 | ||
|
|
a2b19d6158 | ||
|
|
e485441123 | ||
|
|
e564136c22 | ||
|
|
663013ce64 | ||
|
|
c41152e620 | ||
|
|
502e9d66f6 | ||
|
|
e8e1ba0db9 | ||
|
|
29ba5973b6 | ||
|
|
75efd6b1c3 | ||
|
|
f58db1c9e8 | ||
|
|
ff65f688f7 | ||
|
|
af91eee2cc |
@@ -158,6 +158,15 @@ make amalgamate
|
||||
Running `make amalgamate` will also apply automatic formatting to the source files using
|
||||
[`Artistic Style`](https://astyle.sourceforge.net/). This formatting may modify your source files in-place. Be certain to review and commit any changes to avoid unintended formatting diffs in commits.
|
||||
|
||||
If you add, rename, or remove a header in `include/nlohmann`, also regenerate the header list in
|
||||
[`BUILD.bazel`](https://github.com/nlohmann/json/blob/develop/BUILD.bazel) (requires CMake) by executing:
|
||||
|
||||
```shell
|
||||
make BUILD.bazel
|
||||
```
|
||||
|
||||
The amalgamation check in CI fails if any of these generated files is out of date.
|
||||
|
||||
## Recommended documentation
|
||||
|
||||
- The library’s [README file](https://github.com/nlohmann/json/blob/master/README.md) is an excellent starting point to
|
||||
|
||||
@@ -29,6 +29,27 @@ labels:
|
||||
files:
|
||||
- ".github/external_ci/.*"
|
||||
|
||||
- label: "CI"
|
||||
files:
|
||||
- ".github/(dependabot|labeler)\\.yml"
|
||||
|
||||
- label: "aspect: binary formats"
|
||||
files:
|
||||
- "include/nlohmann/detail/input/binary_reader\\.hpp"
|
||||
- "include/nlohmann/detail/output/binary_writer\\.hpp"
|
||||
- "tests/src/unit-(bson|cbor|msgpack|ubjson|bjdata|bon8|binary_formats)"
|
||||
- "tests/src/fuzzer-parse_(bson|cbor|msgpack|ubjson|bjdata|bon8)"
|
||||
- "docs/mkdocs/docs/features/binary_formats/"
|
||||
- "docs/mkdocs/docs/(api/basic_json|examples)/(to|from)_(bson|cbor|msgpack|ubjson|bjdata|bon8)"
|
||||
|
||||
- label: "aspect: binary formats"
|
||||
title: "(?i)(bson|cbor|msgpack|messagepack|ubjson|bjdata|bon8|binary format)"
|
||||
|
||||
- label: "python"
|
||||
files:
|
||||
- "\\.py$"
|
||||
- "requirements[^/]*\\.txt$"
|
||||
|
||||
- label: "S"
|
||||
size-below: 10
|
||||
- label: "M"
|
||||
|
||||
@@ -57,13 +57,16 @@ jobs:
|
||||
python3 -mvenv venv
|
||||
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||
|
||||
- name: Regenerate amalgamation and formatting
|
||||
- name: Regenerate amalgamation, formatting, and BUILD.bazel
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
|
||||
python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json.json -s .
|
||||
python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json_fwd.json -s .
|
||||
|
||||
# the header list of the Bazel "json" target must match the files in include/
|
||||
cmake -P cmake/scripts/gen_bazel_build_file.cmake
|
||||
|
||||
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
||||
$INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp
|
||||
|
||||
@@ -87,7 +90,7 @@ jobs:
|
||||
mkdir -p ${{ github.workspace }}/patch
|
||||
git diff --patch --no-color > ${{ github.workspace }}/patch/amalgamation.patch
|
||||
if [ -s ${{ github.workspace }}/patch/amalgamation.patch ]; then
|
||||
echo "The source code has not been amalgamated/formatted correctly. Diff:"
|
||||
echo "The source code has not been amalgamated/formatted correctly or BUILD.bazel is out of date. Diff:"
|
||||
cat ${{ github.workspace }}/patch/amalgamation.patch
|
||||
echo "has_diff=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
|
||||
@@ -95,13 +95,13 @@ jobs:
|
||||
issue_number: issue_number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly.'
|
||||
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly, or `BUILD.bazel` is out of date.'
|
||||
+ (hasPatch ? '\n\n📎 A ready-to-apply patch is attached to the [failed workflow run](' + runUrl + ') as the `amalgamation-patch` artifact.'
|
||||
+ ' Download it, then apply it locally from the repository root with:'
|
||||
+ '\n\n```shell\ngit apply amalgamation.patch\n```\n\n'
|
||||
+ 'This does not require installing astyle yourself.'
|
||||
: '')
|
||||
+ (first ? '\n\n@' + author + ' Please read and follow the [Contribution Guidelines]'
|
||||
+ '(https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#files-to-change).'
|
||||
+ '(https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#amalgamate-the-source-code).'
|
||||
: '')
|
||||
})
|
||||
|
||||
@@ -43,6 +43,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
|
||||
@@ -76,6 +76,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
container: ubuntu:focal
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_simdutf]
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||
steps:
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||
|
||||
@@ -124,11 +124,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Run CMake (Release)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
if: matrix.build_type == 'Release'
|
||||
shell: pwsh
|
||||
- name: Run CMake (Debug)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
if: matrix.build_type == 'Debug'
|
||||
shell: pwsh
|
||||
- name: Build
|
||||
@@ -158,6 +158,10 @@ jobs:
|
||||
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
|
||||
# MinGW linker cannot relocate the debug sections this test produces.
|
||||
# The tests are only built and run here, so the debug info is not used.
|
||||
# Do not add -O1 here to shrink the objects further: it does make them
|
||||
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
|
||||
# before doctest prints its first line - 39 of 102 tests on clang 18.
|
||||
# Keep the objects small by splitting the test files instead.
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build ^
|
||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
||||
|
||||
@@ -30,8 +30,10 @@ cc_library(
|
||||
"include/nlohmann/detail/input/input_adapters.hpp",
|
||||
"include/nlohmann/detail/input/json_sax.hpp",
|
||||
"include/nlohmann/detail/input/lexer.hpp",
|
||||
"include/nlohmann/detail/input/number_parse.hpp",
|
||||
"include/nlohmann/detail/input/parser.hpp",
|
||||
"include/nlohmann/detail/input/position_t.hpp",
|
||||
"include/nlohmann/detail/input/string_scan.hpp",
|
||||
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
||||
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
||||
"include/nlohmann/detail/iterators/iteration_proxy.hpp",
|
||||
@@ -49,12 +51,14 @@ cc_library(
|
||||
"include/nlohmann/detail/meta/detected.hpp",
|
||||
"include/nlohmann/detail/meta/identity_tag.hpp",
|
||||
"include/nlohmann/detail/meta/is_sax.hpp",
|
||||
"include/nlohmann/detail/meta/logic.hpp",
|
||||
"include/nlohmann/detail/meta/std_fs.hpp",
|
||||
"include/nlohmann/detail/meta/type_traits.hpp",
|
||||
"include/nlohmann/detail/meta/void_t.hpp",
|
||||
"include/nlohmann/detail/output/binary_writer.hpp",
|
||||
"include/nlohmann/detail/output/output_adapters.hpp",
|
||||
"include/nlohmann/detail/output/serializer.hpp",
|
||||
"include/nlohmann/detail/recursion_depth_limit.hpp",
|
||||
"include/nlohmann/detail/string_concat.hpp",
|
||||
"include/nlohmann/detail/string_escape.hpp",
|
||||
"include/nlohmann/detail/string_utils.hpp",
|
||||
|
||||
@@ -59,6 +59,7 @@ option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value compar
|
||||
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
||||
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
||||
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF)
|
||||
option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF)
|
||||
|
||||
if (JSON_CI)
|
||||
include(ci)
|
||||
@@ -108,6 +109,10 @@ if (JSON_Diagnostics)
|
||||
message(STATUS "Diagnostics enabled (JSON_DIAGNOSTICS=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_StrictNulHandling)
|
||||
message(STATUS "Strict NUL-byte handling enabled (JSON_STRICT_NUL_HANDLING=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_Diagnostic_Positions)
|
||||
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
|
||||
endif()
|
||||
@@ -141,6 +146,7 @@ target_compile_definitions(
|
||||
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
|
||||
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
@@ -250,12 +250,16 @@ Further documentation:
|
||||
|
||||
### `BUILD.bazel`
|
||||
|
||||
The file can be updated by calling
|
||||
The build definition for [Bazel](https://bazel.build). The file is generated by
|
||||
`cmake/scripts/gen_bazel_build_file.cmake`, which derives the header list from the files in `include`; change the
|
||||
script rather than editing the file by hand. The file can be updated by calling
|
||||
|
||||
```shell
|
||||
make BUILD.bazel
|
||||
```
|
||||
|
||||
The "Check amalgamation" workflow fails if the file is out of date.
|
||||
|
||||
### `meson.build`
|
||||
|
||||
The build definition for the [Meson](https://mesonbuild.com) build system.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel
|
||||
|
||||
##########################################################################
|
||||
# configuration
|
||||
@@ -30,11 +30,13 @@ AMALGAMATED_FWD_FILE=single_include/nlohmann/json_fwd.hpp
|
||||
# main target
|
||||
all:
|
||||
@echo "amalgamate - amalgamate files single_include/nlohmann/json{,_fwd}.hpp from the include/nlohmann sources"
|
||||
@echo "BUILD.bazel - regenerate the Bazel BUILD file from the include/nlohmann sources"
|
||||
@echo "ChangeLog.md - generate ChangeLog file"
|
||||
@echo "check-amalgamation - check whether sources have been amalgamated"
|
||||
@echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date"
|
||||
@echo "clean - remove built files"
|
||||
@echo "doctest - compile example files and check their output"
|
||||
@echo "fuzz_testing - prepare fuzz testing of the JSON parser"
|
||||
@echo "fuzz_testing_bon8 - prepare fuzz testing of the BON8 parser"
|
||||
@echo "fuzz_testing_bson - prepare fuzz testing of the BSON parser"
|
||||
@echo "fuzz_testing_cbor - prepare fuzz testing of the CBOR parser"
|
||||
@echo "fuzz_testing_msgpack - prepare fuzz testing of the MessagePack parser"
|
||||
@@ -70,6 +72,14 @@ fuzz_testing:
|
||||
find tests/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_bon8:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_bon8_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_bon8_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data -size -5k -name *.bon8 | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_bson:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
@@ -172,8 +182,13 @@ check-amalgamation:
|
||||
@diff $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE) ; false)
|
||||
@mv $(AMALGAMATED_FILE)~ $(AMALGAMATED_FILE)
|
||||
@mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE)
|
||||
@mv BUILD.bazel BUILD.bazel~
|
||||
@$(MAKE) BUILD.bazel
|
||||
@diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false)
|
||||
@mv BUILD.bazel~ BUILD.bazel
|
||||
|
||||
BUILD.bazel: $(SRCS)
|
||||
# generate the Bazel BUILD file; phony, because a removed header would not trigger a rebuild
|
||||
BUILD.bazel:
|
||||
cmake -P cmake/scripts/gen_bazel_build_file.cmake
|
||||
|
||||
##########################################################################
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
- [Implicit conversions](#implicit-conversions)
|
||||
- [Conversions to/from arbitrary types](#arbitrary-types-conversions)
|
||||
- [Specializing enum conversion](#specializing-enum-conversion)
|
||||
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)
|
||||
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, BJData, and BON8)](#binary-formats-bson-cbor-messagepack-ubjson-bjdata-and-bon8)
|
||||
- [Customers](#customers)
|
||||
- [Ecosystem](#ecosystem)
|
||||
- [Supported compilers](#supported-compilers)
|
||||
@@ -128,7 +128,7 @@ There is also a [**docset**](https://github.com/Kapeli/Dash-User-Contributions/t
|
||||
- **JSON Pointer functions**: [flatten](https://json.nlohmann.me/api/basic_json/flatten), [unflatten](https://json.nlohmann.me/api/basic_json/unflatten)
|
||||
- **JSON Patch functions**: [patch](https://json.nlohmann.me/api/basic_json/patch), [patch_inplace](https://json.nlohmann.me/api/basic_json/patch_inplace), [diff](https://json.nlohmann.me/api/basic_json/diff), [merge_patch](https://json.nlohmann.me/api/basic_json/merge_patch)
|
||||
- **Static functions**: [meta](https://json.nlohmann.me/api/basic_json/meta), [get_allocator](https://json.nlohmann.me/api/basic_json/get_allocator)
|
||||
- **Binary formats**: [from_bjdata](https://json.nlohmann.me/api/basic_json/from_bjdata), [from_bson](https://json.nlohmann.me/api/basic_json/from_bson), [from_cbor](https://json.nlohmann.me/api/basic_json/from_cbor), [from_msgpack](https://json.nlohmann.me/api/basic_json/from_msgpack), [from_ubjson](https://json.nlohmann.me/api/basic_json/from_ubjson), [to_bjdata](https://json.nlohmann.me/api/basic_json/to_bjdata), [to_bson](https://json.nlohmann.me/api/basic_json/to_bson), [to_cbor](https://json.nlohmann.me/api/basic_json/to_cbor), [to_msgpack](https://json.nlohmann.me/api/basic_json/to_msgpack), [to_ubjson](https://json.nlohmann.me/api/basic_json/to_ubjson)
|
||||
- **Binary formats**: [from_bjdata](https://json.nlohmann.me/api/basic_json/from_bjdata), [from_bon8](https://json.nlohmann.me/api/basic_json/from_bon8), [from_bson](https://json.nlohmann.me/api/basic_json/from_bson), [from_cbor](https://json.nlohmann.me/api/basic_json/from_cbor), [from_msgpack](https://json.nlohmann.me/api/basic_json/from_msgpack), [from_ubjson](https://json.nlohmann.me/api/basic_json/from_ubjson), [to_bjdata](https://json.nlohmann.me/api/basic_json/to_bjdata), [to_bon8](https://json.nlohmann.me/api/basic_json/to_bon8), [to_bson](https://json.nlohmann.me/api/basic_json/to_bson), [to_cbor](https://json.nlohmann.me/api/basic_json/to_cbor), [to_msgpack](https://json.nlohmann.me/api/basic_json/to_msgpack), [to_ubjson](https://json.nlohmann.me/api/basic_json/to_ubjson)
|
||||
- **Non-member functions**: [operator<<](https://json.nlohmann.me/api/operator_ltlt/), [operator>>](https://json.nlohmann.me/api/operator_gtgt/), [to_string](https://json.nlohmann.me/api/basic_json/to_string)
|
||||
- **Literals**: [operator""_json](https://json.nlohmann.me/api/operator_literal_json)
|
||||
- **Helper classes**: [std::hash<basic_json>](https://json.nlohmann.me/api/basic_json/std_hash), [std::swap<basic_json>](https://json.nlohmann.me/api/basic_json/std_swap)
|
||||
@@ -1110,9 +1110,9 @@ Other Important points:
|
||||
- When using `get<ENUM_TYPE>()`, undefined JSON values will default to the first pair specified in your map. Select this default pair carefully. If you desire an exception in this circumstance use `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT()` which behaves identically except for throwing an exception on unrecognized values.
|
||||
- If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the map will be returned when converting to or from JSON.
|
||||
|
||||
### Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)
|
||||
### Binary formats (BSON, CBOR, MessagePack, UBJSON, BJData, and BON8)
|
||||
|
||||
Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [BSON](https://bsonspec.org) (Binary JSON), [CBOR](https://cbor.io) (Concise Binary Object Representation), [MessagePack](https://msgpack.org), [UBJSON](https://ubjson.org) (Universal Binary JSON Specification) and [BJData](https://neurojson.org/bjdata) (Binary JData) to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [BSON](https://bsonspec.org) (Binary JSON), [CBOR](https://cbor.io) (Concise Binary Object Representation), [MessagePack](https://msgpack.org), [UBJSON](https://ubjson.org) (Universal Binary JSON Specification), [BJData](https://neurojson.org/bjdata) (Binary JData), and [BON8](https://github.com/hikoworks/hikogui/blob/main/docs/BON8.md) (Binary Object Notation 8) to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
|
||||
```cpp
|
||||
// create a JSON value
|
||||
@@ -1149,6 +1149,14 @@ std::vector<std::uint8_t> v_ubjson = json::to_ubjson(j);
|
||||
|
||||
// roundtrip
|
||||
json j_from_ubjson = json::from_ubjson(v_ubjson);
|
||||
|
||||
// serialize to BON8
|
||||
std::vector<std::uint8_t> v_bon8 = json::to_bon8(j);
|
||||
|
||||
// 0x88, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xF9, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x90
|
||||
|
||||
// roundtrip
|
||||
json j_from_bon8 = json::from_bon8(v_bon8);
|
||||
```
|
||||
|
||||
The library also supports binary types from BSON, CBOR (byte strings), and MessagePack (bin, ext, fixext). They are stored by default as `std::vector<std::uint8_t>` to be processed outside the library.
|
||||
@@ -1421,7 +1429,7 @@ I deeply appreciate the help of the following people.
|
||||
6. [Joshua C. Randall](https://github.com/jrandall) fixed a bug in the floating-point serialization.
|
||||
7. [Aaron Burghardt](https://github.com/aburgh) implemented code to parse streams incrementally. Furthermore, he greatly improved the parser class by allowing the definition of a filter function to discard undesired elements while parsing.
|
||||
8. [Daniel Kopeček](https://github.com/dkopecek) fixed a bug in the compilation with GCC 5.0.
|
||||
9. [Florian Weber](https://github.com/Florianjw) fixed a bug in and improved the performance of the comparison operators.
|
||||
9. [Fiona Johanna Weber](https://github.com/Fiona-J-W) fixed a bug in and improved the performance of the comparison operators.
|
||||
10. [Eric Cornelius](https://github.com/EricMCornelius) pointed out a bug in the handling with NaN and infinity values. He also improved the performance of the string escaping.
|
||||
11. [易思龙](https://github.com/likebeta) implemented a conversion from anonymous enums.
|
||||
12. [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual Studio.
|
||||
@@ -1523,14 +1531,14 @@ I deeply appreciate the help of the following people.
|
||||
108. [Kevin Tonon](https://github.com/ktonon) overworked the C++11 compiler checks in CMake.
|
||||
109. [Axel Huebl](https://github.com/ax3l) simplified a CMake check and added support for the [Spack package manager](https://spack.io).
|
||||
110. [Carlos O'Ryan](https://github.com/coryan) fixed a typo.
|
||||
111. [James Upjohn](https://github.com/jammehcow) fixed a version number in the compilers section.
|
||||
111. [James Upjohn](https://github.com/jupjohn) fixed a version number in the compilers section.
|
||||
112. [Chuck Atkins](https://github.com/chuckatkins) adjusted the CMake files to the CMake packaging guidelines and provided documentation for the CMake integration.
|
||||
113. [Jan Schöppach](https://github.com/dns13) fixed a typo.
|
||||
114. [martin-mfg](https://github.com/martin-mfg) fixed a typo.
|
||||
115. [Matthias Möller](https://github.com/TinyTinni) removed the dependency from `std::stringstream`.
|
||||
116. [agrianius](https://github.com/agrianius) added code to use alternative string implementations.
|
||||
117. [Daniel599](https://github.com/Daniel599) allowed to use more algorithms with the `items()` function.
|
||||
118. [Julius Rakow](https://github.com/jrakow) fixed the Meson include directory and fixed the links to [cppreference.com](https://cppreference.com).
|
||||
118. [Julius Rakow](https://github.com/juliusrakow) fixed the Meson include directory and fixed the links to [cppreference.com](https://cppreference.com).
|
||||
119. [Sonu Lohani](https://github.com/sonulohani) fixed the compilation with MSVC 2015 in debug mode.
|
||||
120. [grembo](https://github.com/grembo) fixed the test suite and re-enabled several test cases.
|
||||
121. [Hyeon Kim](https://github.com/simnalamburt) introduced the macro `JSON_INTERNAL_CATCH` to control the exception handling inside the library.
|
||||
@@ -1581,7 +1589,7 @@ I deeply appreciate the help of the following people.
|
||||
166. [Mark Beckwith](https://github.com/wythe) fixed a typo.
|
||||
167. [yann-morin-1998](https://github.com/yann-morin-1998) helped to reduce the CMake requirement to version 3.1.
|
||||
168. [Konstantin Podsvirov](https://github.com/podsvirov) maintains a package for the MSYS2 software distro.
|
||||
169. [remyabel](https://github.com/remyabel) added GNUInstallDirs to the CMake files.
|
||||
169. [remyabel](https://github.com/remyabel2) added GNUInstallDirs to the CMake files.
|
||||
170. [Taylor Howard](https://github.com/taylorhoward92) fixed a unit test.
|
||||
171. [Gabe Ron](https://github.com/Macr0Nerd) implemented the `to_string` method.
|
||||
172. [Watal M. Iwasaki](https://github.com/heavywatal) fixed a Clang warning.
|
||||
@@ -1608,7 +1616,7 @@ I deeply appreciate the help of the following people.
|
||||
193. [Hubert Chathi](https://github.com/uhoreg) made CMake's version config file architecture-independent.
|
||||
194. [OmnipotentEntity](https://github.com/OmnipotentEntity) implemented the binary values for CBOR, MessagePack, BSON, and UBJSON.
|
||||
195. [ArtemSarmini](https://github.com/ArtemSarmini) fixed a compilation issue with GCC 10 and fixed a leak.
|
||||
196. [Evgenii Sopov](https://github.com/sea-kg) integrated the library to the wsjcpp package manager.
|
||||
196. [Evgenii Sopov](https://github.com/sea5kg) integrated the library to the wsjcpp package manager.
|
||||
197. [Sergey Linev](https://github.com/linev) fixed a compiler warning.
|
||||
198. [Miguel Magalhães](https://github.com/magamig) fixed the year in the copyright.
|
||||
199. [Gareth Sylvester-Bradley](https://github.com/garethsb-sony) fixed a compilation issue with MSVC.
|
||||
@@ -1702,7 +1710,7 @@ I deeply appreciate the help of the following people.
|
||||
287. [NN](https://github.com/NN---) added the Visual Studio output directory to `.gitignore`.
|
||||
288. [Romain Reignier](https://github.com/romainreignier) improved the performance of the vector output adapter.
|
||||
289. [Mike](https://github.com/Mike-Leo-Smith) fixed the `std::iterator_traits`.
|
||||
290. [Richard Hozák](https://github.com/zxey) added macro `JSON_NO_ENUM` to disable default enum conversions.
|
||||
290. [Richard Hozák](https://github.com/richardhozak) added macro `JSON_NO_ENUM` to disable default enum conversions.
|
||||
291. [vakokako](https://github.com/vakokako) fixed tests when compiling with C++20.
|
||||
292. [Alexander “weej” Jones](https://github.com/alexweej) fixed an example in the README.
|
||||
293. [Eli Schwartz](https://github.com/eli-schwartz) added more files to the `include.zip` archive.
|
||||
@@ -1727,7 +1735,7 @@ I deeply appreciate the help of the following people.
|
||||
312. [Gareth Sylvester-Bradley](https://github.com/garethsb) added `operator/=` and `operator/` to construct JSON pointers.
|
||||
313. [Michael Macnair](https://github.com/mykter) added support for afl-fuzz testing.
|
||||
314. [Berkus Decker](https://github.com/berkus) fixed a typo in the README.
|
||||
315. [Illia Polishchuk](https://github.com/effolkronium) improved the CMake testing.
|
||||
315. [Illia Polishchuk](https://github.com/ilqvya) improved the CMake testing.
|
||||
316. [Ikko Ashimine](https://github.com/eltociear) fixed a typo.
|
||||
317. [Raphael Grimm](https://github.com/barcode) added the possibility to define a custom base class.
|
||||
318. [tocic](https://github.com/tocic) fixed typos in the documentation.
|
||||
@@ -1797,6 +1805,66 @@ I deeply appreciate the help of the following people.
|
||||
382. [bitFiedler](https://github.com/bitFiedler) made GDB pretty printer work with Python 3.8.
|
||||
383. [Gianfranco Costamagna](https://github.com/LocutusOfBorg) fixed a compiler warning.
|
||||
384. [risa2000](https://github.com/risa2000) made `std::filesystem::path` conversion to/from UTF-8 encoded string explicit.
|
||||
385. [AM](https://github.com/maqnouch) fixed typos in the README.
|
||||
386. [dmenendez-gruposantander](https://github.com/dmenendez-gruposantander) fixed typos in the comments of the examples.
|
||||
387. [Mihai Stan](https://github.com/mstan-xx) fixed comparisons against the literal `0`.
|
||||
388. [Matt Gumbel](https://github.com/intelmatt) fixed some `-Weffc++` warnings.
|
||||
389. [vimpunk](https://github.com/vimpunk) moved a lambda out of an unevaluated context to support older compilers.
|
||||
390. [Chris Harris](https://github.com/cjh1) fixed the compilation with GCC 4.8.
|
||||
391. [Palmer Dabbelt](https://github.com/palmer-dabbelt) generated and installed a pkg-config file.
|
||||
392. [Gus Pozuelo](https://github.com/ap-viavi) made `ordered_map` compatible with GCC 5.5, Clang 3.6, and Xcode 9.
|
||||
393. [AK](https://github.com/Lioncky) fixed an MSVC build error caused by the `min`/`max` macros from `windows.h`.
|
||||
394. [Sergiu Deitsch](https://github.com/sergiud) provided a fallback for missing `char8_t` support.
|
||||
395. [Xiaochuan Ye](https://github.com/XueSongTap) fixed `from_msgpack` for `std::byte` input by specializing `std::char_traits`.
|
||||
396. [Ville Vesilehto](https://github.com/thevilledev) fixed an overflow in the BJData size calculation and rejected overflowing negative integers in CBOR.
|
||||
397. [NmPassTHFan](https://github.com/nmpassthf) replaced the deprecated `std::is_trivial` for C++26.
|
||||
398. [Chris Ever](https://github.com/chirsz-ever) added the `ignore_trailing_commas` parser option.
|
||||
399. [Kuan-Fu Wu](https://github.com/kfwu1999) fixed the example code for `json_pointer` initialization.
|
||||
400. [David Kilzer](https://github.com/ddkilzer) added a missing header to the input adapters.
|
||||
401. [Miko](https://github.com/mikomikotaishi) added proper C++20 module support, simplified the module API, and fixed missing exports.
|
||||
402. [hitgirl](https://github.com/hitgil) fixed the CMake configuration when cross-compiling.
|
||||
403. [Devon Thomas](https://github.com/ThomaDevOSU) mentioned the Artistic Style formatting in the contribution guidelines.
|
||||
404. [Erik Hu](https://github.com/Erikhu1) made Coveralls upload errors non-fatal in the CI.
|
||||
405. [co63oc](https://github.com/co63oc) fixed typos.
|
||||
406. [DmitriBogdanov](https://github.com/DmitriBogdanov) fixed broken package manager links in the documentation.
|
||||
407. [Bander](https://github.com/banderzhm) improved the MSVC compatibility of the C++ modules.
|
||||
408. [Andy Choi](https://github.com/ccpong) removed an unnecessary `template` keyword before `get` in the README and the documentation.
|
||||
409. [SamareshSingh](https://github.com/ssam18) fixed single-element brace initialization to copy/move instead of wrapping in an array, fixed the `WITH_DEFAULT` macros for `ordered_map`, and handled moved events in `serve_header.py`.
|
||||
410. [Aditya](https://github.com/Lumowhisp) improved the documentation of the documentation generation.
|
||||
411. [cheese1](https://github.com/cheese1) clarified the README.
|
||||
412. [KhloodElhossiny](https://github.com/khloodelhossiny) enabled `std::string_view` keys in `operator[]`.
|
||||
413. [Charles Cabergs](https://github.com/cacharle) fixed a `-Wtautological-constant-out-of-range-compare` warning.
|
||||
414. [EALePain](https://github.com/EALePain) made the `std::tuple` conversion work with reference types such as `std::tie`.
|
||||
415. [koala_oishi](https://github.com/chibi-dogs) fixed grammatical wording in the README.
|
||||
416. [riccardoori11](https://github.com/riccardoori11) fixed a typo in the documentation.
|
||||
417. [Swastik Bose](https://github.com/VasuBhakt) fixed the parent pointers after `update()` with `JSON_DIAGNOSTICS` and fixed the Doxygen autolinking of requirements.
|
||||
418. [trdesilva](https://github.com/trdesilva) added `front`, `pop_front`, and `push_front` to `json_pointer`.
|
||||
419. [Akhilesh Arora](https://github.com/akhilesharora) fixed an incomplete-type error with `ordered_json`.
|
||||
420. [Hariom Phulre](https://github.com/hariomphulre) fixed the C++20 modules compilation with GCC.
|
||||
421. [Kirill Lokotkov](https://github.com/RUSLoker) fixed printing `long double` values.
|
||||
422. [George Sedov](https://github.com/radistmorse) added the `NLOHMANN_DEFINE_TYPE_*_WITH_NAMES` macros.
|
||||
423. [Caillin Nugent](https://github.com/nugentcaillin) added the `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` macro.
|
||||
424. [Cosmin D.](https://github.com/drcosmin) fixed `std::filesystem::path` conversions and added an MSVC workaround for `std::unique_ptr`.
|
||||
425. [Paul Dreik](https://github.com/pauldreik) fixed a test relying on implementation-specific behavior.
|
||||
426. [Daniel Falk](https://github.com/daniel-falk) added missing copyright notices to the SBOM.
|
||||
427. [Federico Sfriso](https://github.com/federicosfriso05-dotcom) added support for constructing JSON values from C++20 range views.
|
||||
428. [Luke Banicevic](https://github.com/banaboi) fixed corrupt BSON output for lengths exceeding `INT32_MAX`, cleaned up the BSON writer, and improved the documentation.
|
||||
429. [Patrick Armstrong](https://github.com/Patrick10199) updated the CBOR references and the half-precision float assertions.
|
||||
430. [Yash Bavadiya](https://github.com/xevrion) added checks to all BSON reads.
|
||||
431. [hum4nBeing](https://github.com/hum4nBeing) fixed the overflow handling of high-precision numbers in UBJSON.
|
||||
432. [tomatotomata](https://github.com/tomatotomata) added checks for reading CBOR tagged subtypes.
|
||||
433. [YingqiDuan](https://github.com/YingqiDuan) documented the BSON interoperability.
|
||||
434. [KBS](https://github.com/youdie006) documented the standards compliance and the strictness of `parse()` and `operator>>`.
|
||||
435. [Petr Bělohlávek](https://github.com/petrbel) added Clang 21 and 22 to the CI.
|
||||
436. [Dmitry Rantovov](https://github.com/darkdi) fixed the placement of a CBOR documentation block.
|
||||
437. [ljcjclljc](https://github.com/ljcjclljc) fixed the comparison of large unsigned integers with signed integers.
|
||||
438. [Sahil Kamate](https://github.com/sahilkamate03) fixed the handling of CBOR tags 0-5 and 21-23.
|
||||
439. [Krishnanand G](https://github.com/Krishnanand-G) made the UBJSON writer reject `use_type` without `use_size`.
|
||||
440. [whn](https://github.com/Whning0513) documented the lenient BSON input handling and corrected the complexity of `to_bson`.
|
||||
441. [elix3r](https://github.com/22elix3r) fixed `update()` with `merge_objects` when merging a primitive into an object.
|
||||
442. [Avionic Harshit](https://github.com/avionicharshit-byte) made `diff()` linear when an array shrinks.
|
||||
443. [Qatadaha Bin Matloob](https://github.com/qatcod) fixed comparisons between integers and floats and fixed unparsable BJData output.
|
||||
444. [Wu Shuwen](https://github.com/dajiaohuang) removed an unused include.
|
||||
|
||||
Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone.
|
||||
|
||||
|
||||
+64
-9
@@ -231,18 +231,20 @@ add_custom_target(ci_test_simdutf
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Enable brace-init copy semantics.
|
||||
# Enable strict NUL-byte handling.
|
||||
###############################################################################
|
||||
|
||||
add_custom_target(ci_test_brace_init_copy_semantics
|
||||
add_custom_target(ci_test_strict_nul_handling
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
||||
-DCMAKE_CXX_FLAGS=-DJSON_BRACE_INIT_COPY_SEMANTICS=1
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
COMMENT "Compile and test with brace-init copy semantics enabled"
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_StrictNulHandling=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_strict_nul_handling
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_strict_nul_handling
|
||||
# unit-testsuites contains a fixture (a "1e308" test value) that relies on the
|
||||
# legacy NUL-as-end-of-input behavior this macro disables; exclude it here, as
|
||||
# it is expected to fail under strict NUL handling and is out of scope for it
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_strict_nul_handling && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure -E "test-testsuites"
|
||||
COMMENT "Compile and test with strict NUL-byte handling enabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
@@ -260,6 +262,59 @@ add_custom_target(ci_test_noglobaludls
|
||||
COMMENT "Compile and test with global UDLs disabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Disable enum serialization.
|
||||
###############################################################################
|
||||
|
||||
add_custom_target(ci_test_disableenumserialization
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DisableEnumSerialization=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_disableenumserialization
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_disableenumserialization
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_disableenumserialization && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
COMMENT "Compile and test with enum serialization disabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Skip the multiple-inclusion library version check.
|
||||
###############################################################################
|
||||
|
||||
# tests/src/skip_library_version_check.cpp deliberately simulates a scenario
|
||||
# (mixing two differently-versioned inclusions of the library in one
|
||||
# translation unit) that unavoidably triggers the compiler's own "macro
|
||||
# redefined" warning, so -- unlike the ci_test_* targets above -- it is
|
||||
# compiled directly here, with a modest warning set, instead of being folded
|
||||
# into the library's own -Weverything/-Werror unit test matrix.
|
||||
add_custom_target(ci_test_skiplibraryversioncheck
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/skip_library_version_check
|
||||
COMMAND ${CMAKE_CXX_COMPILER} -std=c++11 -Wall -Wextra
|
||||
-I${PROJECT_SOURCE_DIR}/include
|
||||
${PROJECT_SOURCE_DIR}/tests/src/skip_library_version_check.cpp
|
||||
-o ${PROJECT_BINARY_DIR}/skip_library_version_check/skip_library_version_check
|
||||
COMMAND ${PROJECT_BINARY_DIR}/skip_library_version_check/skip_library_version_check
|
||||
COMMENT "Compile and run a translation unit simulating a mismatched library version, with JSON_SKIP_LIBRARY_VERSION_CHECK defined"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Disable thread-local storage.
|
||||
###############################################################################
|
||||
|
||||
# Without thread-local storage, the copy constructor cannot bound its descent
|
||||
# and copies every object and array without the call stack. That path is
|
||||
# otherwise only reached by values nested deeper than the bound, so this target
|
||||
# is what runs the whole test suite through it.
|
||||
add_custom_target(ci_test_no_thread_local
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON
|
||||
-DCMAKE_CXX_FLAGS=-DJSON_NO_THREAD_LOCAL
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_no_thread_local
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_no_thread_local
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_no_thread_local && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
COMMENT "Compile and test without thread-local storage"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Coverage.
|
||||
###############################################################################
|
||||
@@ -487,7 +542,7 @@ add_custom_target(ci_infer
|
||||
|
||||
add_custom_target(ci_offline_testdata
|
||||
COMMAND mkdir -p ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data && ${GIT_TOOL} clone -c advice.detachedHead=false --branch v3.1.0 https://github.com/nlohmann/json_test_data.git --quiet --depth 1
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data && ${GIT_TOOL} clone -c advice.detachedHead=false --branch v3.2.0 https://github.com/nlohmann/json_test_data.git --quiet --depth 1
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_TestDataDirectory=${PROJECT_BINARY_DIR}/build_offline_testdata/test_data/json_test_data
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
set(JSON_TEST_DATA_URL https://github.com/nlohmann/json_test_data)
|
||||
set(JSON_TEST_DATA_VERSION 3.1.0)
|
||||
set(JSON_TEST_DATA_VERSION 3.2.0)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
|
||||
@@ -1,24 +1,58 @@
|
||||
# generate Bazel BUILD file
|
||||
#
|
||||
# usage: cmake -P cmake/scripts/gen_bazel_build_file.cmake (or: make BUILD.bazel)
|
||||
#
|
||||
# The header list of the "json" target is derived from the files in include/. Everything else is fixed text below,
|
||||
# so edit this script rather than BUILD.bazel.
|
||||
|
||||
set(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../..")
|
||||
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
|
||||
set(BUILD_FILE "${PROJECT_ROOT}/BUILD.bazel")
|
||||
|
||||
file(GLOB_RECURSE HEADERS LIST_DIRECTORIES false RELATIVE "${PROJECT_ROOT}" "include/*.hpp")
|
||||
file(GLOB_RECURSE HEADERS LIST_DIRECTORIES false RELATIVE "${PROJECT_ROOT}" "${PROJECT_ROOT}/include/*.hpp")
|
||||
list(SORT HEADERS)
|
||||
|
||||
set(CONTENT [=[
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
load("@rules_license//rules:license.bzl", "license")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = [":license"],
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"LICENSE.MIT",
|
||||
])
|
||||
|
||||
license(
|
||||
name = "license",
|
||||
license_kinds = ["@rules_license//licenses/spdx:MIT"],
|
||||
license_text = "LICENSE.MIT",
|
||||
)
|
||||
|
||||
file(WRITE "${BUILD_FILE}" [=[
|
||||
cc_library(
|
||||
name = "json",
|
||||
hdrs = [
|
||||
]=])
|
||||
|
||||
foreach(header ${HEADERS})
|
||||
file(APPEND "${BUILD_FILE}" " \"${header}\",\n")
|
||||
string(APPEND CONTENT " \"${header}\",\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND "${BUILD_FILE}" [=[
|
||||
string(APPEND CONTENT [=[
|
||||
],
|
||||
includes = ["include"],
|
||||
visibility = ["//visibility:public"],
|
||||
alwayslink = True,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "singleheader-json",
|
||||
hdrs = [
|
||||
"single_include/nlohmann/json.hpp",
|
||||
],
|
||||
includes = ["single_include"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
]=])
|
||||
|
||||
file(WRITE "${BUILD_FILE}" "${CONTENT}")
|
||||
|
||||
@@ -48,6 +48,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bjdata', 'Fu
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bson', 'Function', 'api/basic_json/from_bson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_cbor', 'Function', 'api/basic_json/from_cbor/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_msgpack', 'Function', 'api/basic_json/from_msgpack/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bon8', 'Function', 'api/basic_json/from_bon8/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_ubjson', 'Function', 'api/basic_json/from_ubjson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::front', 'Method', 'api/basic_json/front/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get', 'Method', 'api/basic_json/get/index.html');
|
||||
@@ -121,6 +122,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bjdata', 'Func
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bson', 'Function', 'api/basic_json/to_bson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_cbor', 'Function', 'api/basic_json/to_cbor/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_msgpack', 'Function', 'api/basic_json/to_msgpack/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bon8', 'Function', 'api/basic_json/to_bon8/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_string', 'Method', 'api/basic_json/to_string/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html');
|
||||
@@ -171,6 +173,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BJData', 'Gui
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BSON', 'Guide', 'features/binary_formats/bson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: CBOR', 'Guide', 'features/binary_formats/cbor/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: MessagePack', 'Guide', 'features/binary_formats/messagepack/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BON8', 'Guide', 'features/binary_formats/bon8/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: UBJSON', 'Guide', 'features/binary_formats/ubjson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Values', 'Guide', 'features/binary_values/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Comments', 'Guide', 'features/comments/index.html');
|
||||
|
||||
@@ -90,6 +90,10 @@ Linear in the length of the input. The parser is a predictive LL(1) parser.
|
||||
|
||||
A UTF-8 byte order mark is silently ignored.
|
||||
|
||||
By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input, rather than as an ordinary (and,
|
||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -111,6 +115,8 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
- [sax_parse](sax_parse.md) - parse input using the SAX interface
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -120,6 +126,8 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# <small>nlohmann::basic_json::</small>from_bon8
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
template<typename InputType>
|
||||
static basic_json from_bon8(InputType&& i,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true);
|
||||
// (2)
|
||||
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||
static basic_json from_bon8(IteratorType first, SentinelType last,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true);
|
||||
```
|
||||
|
||||
Deserializes a given input to a JSON value using the BON8 (Binary Object Notation 8) serialization format.
|
||||
|
||||
1. Reads from a compatible input.
|
||||
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support).
|
||||
|
||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bon8.md).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`InputType`
|
||||
: A compatible input, for instance:
|
||||
|
||||
- an `std::istream` object
|
||||
- a `FILE` pointer
|
||||
- a C-style array of characters
|
||||
- a pointer to a null-terminated string of single byte characters
|
||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
||||
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
||||
|
||||
`IteratorType`
|
||||
: a compatible iterator type
|
||||
|
||||
`SentinelType`
|
||||
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||
|
||||
- a custom sentinel type for C++20 ranges
|
||||
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||
|
||||
## Parameters
|
||||
|
||||
`i` (in)
|
||||
: an input in BON8 format convertible to an input adapter
|
||||
|
||||
`first` (in)
|
||||
: iterator to the start of the input
|
||||
|
||||
`last` (in)
|
||||
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||
|
||||
`strict` (in)
|
||||
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
||||
|
||||
`allow_exceptions` (in)
|
||||
: whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default)
|
||||
|
||||
## Return value
|
||||
|
||||
deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be
|
||||
`value_t::discarded`. The latter can be checked with [`is_discarded`](is_discarded.md).
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or
|
||||
the end of the file was not reached when `strict` was set to true
|
||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs, for instance
|
||||
an invalid byte, a string that is not valid UTF-8, or an object key that is not a string
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example shows the deserialization of a byte vector in BON8 format to a JSON value.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/from_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/from_bon8.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [to_bon8](to_bon8.md) create a BON8 serialization of a JSON value
|
||||
- [from_cbor](from_cbor.md) create a JSON value from an input in CBOR format
|
||||
- [from_msgpack](from_msgpack.md) create a JSON value from an input in MessagePack format
|
||||
- [from_bson](from_bson.md) create a JSON value from an input in BSON format
|
||||
- [from_ubjson](from_ubjson.md) create a JSON value from an input in UBJSON format
|
||||
- [from_bjdata](from_bjdata.md) create a JSON value from an input in BJData format
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -290,11 +290,13 @@ Access to the JSON value
|
||||
### Binary formats
|
||||
|
||||
- [**from_bjdata**](from_bjdata.md) (_static_) - create a JSON value from an input in BJData format
|
||||
- [**from_bon8**](from_bon8.md) (_static_) - create a JSON value from an input in BON8 format
|
||||
- [**from_bson**](from_bson.md) (_static_) - create a JSON value from an input in BSON format
|
||||
- [**from_cbor**](from_cbor.md) (_static_) - create a JSON value from an input in CBOR format
|
||||
- [**from_msgpack**](from_msgpack.md) (_static_) - create a JSON value from an input in MessagePack format
|
||||
- [**from_ubjson**](from_ubjson.md) (_static_) - create a JSON value from an input in UBJSON format
|
||||
- [**to_bjdata**](to_bjdata.md) (_static_) - create a BJData serialization of a given JSON value
|
||||
- [**to_bon8**](to_bon8.md) (_static_) - create a BON8 serialization of a given JSON value
|
||||
- [**to_bson**](to_bson.md) (_static_) - create a BSON serialization of a given JSON value
|
||||
- [**to_cbor**](to_cbor.md) (_static_) - create a CBOR serialization of a given JSON value
|
||||
- [**to_msgpack**](to_msgpack.md) (_static_) - create a MessagePack serialization of a given JSON value
|
||||
|
||||
@@ -7,7 +7,8 @@ enum class input_format_t {
|
||||
msgpack,
|
||||
ubjson,
|
||||
bson,
|
||||
bjdata
|
||||
bjdata,
|
||||
bon8
|
||||
};
|
||||
```
|
||||
|
||||
@@ -31,6 +32,9 @@ bson
|
||||
bjdata
|
||||
: BJData (Binary JData)
|
||||
|
||||
bon8
|
||||
: BON8 (Binary Object Notation 8)
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
@@ -88,6 +88,8 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
do not belong to the same JSON value; example: `"iterators do not fit"`
|
||||
- Throws [`invalid_iterator.211`](../../home/exceptions.md#jsonexceptioninvalid_iterator211) if `first` or `last`
|
||||
are iterators into container for which insert is called; example: `"passed iterators may not belong to container"`
|
||||
- Throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) if `first` or `last`
|
||||
do not point to an array; example: `"iterators first and last must point to arrays"`
|
||||
4. The function can throw the following exceptions:
|
||||
- Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if called on JSON values other than
|
||||
arrays; example: `"cannot use insert() with string"`
|
||||
|
||||
@@ -103,6 +103,10 @@ A UTF-8 byte order mark is silently ignored.
|
||||
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
||||
|
||||
By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input, rather than as an ordinary (and,
|
||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Parsing from a character array"
|
||||
@@ -236,6 +240,8 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||
- [accept](accept.md) - check if the input is valid JSON
|
||||
- [sax_parse](sax_parse.md) - parse input using the SAX interface
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -246,6 +252,8 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ class parse_error : public exception;
|
||||
```
|
||||
|
||||
The library throws this exception when a parse error occurs. Parse errors can occur during the deserialization of
|
||||
JSON text, BSON, CBOR, MessagePack, UBJSON, as well as when using JSON Patch.
|
||||
JSON text, BJData, BON8, BSON, CBOR, MessagePack, UBJSON, as well as when using JSON Patch.
|
||||
|
||||
Member `byte` holds the byte index of the last read character in the input file (see note below).
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
: SAX event listener (must not be null)
|
||||
|
||||
`format` (in)
|
||||
: the format to parse (JSON, CBOR, MessagePack, or UBJSON) (optional, `input_format_t::json` by default), see
|
||||
: the format to parse (JSON, BJData, BON8, BSON, CBOR, MessagePack, or UBJSON) (optional, `input_format_t::json` by
|
||||
default), see
|
||||
[`input_format_t`](input_format_t.md) for more information
|
||||
|
||||
`strict` (in)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# <small>nlohmann::basic_json::</small>to_bon8
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j);
|
||||
|
||||
// (2)
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o);
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o);
|
||||
```
|
||||
|
||||
Serializes a given JSON value `j` to a byte vector using the BON8 (Binary Object Notation 8) serialization format. BON8
|
||||
is a compact binary serialization format that stores strings as UTF-8 without a length prefix.
|
||||
|
||||
1. Returns a byte vector containing the BON8 serialization.
|
||||
2. Writes the BON8 serialization to an output adapter.
|
||||
|
||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bon8.md).
|
||||
|
||||
## Parameters
|
||||
|
||||
`j` (in)
|
||||
: JSON value to serialize
|
||||
|
||||
`o` (in)
|
||||
: output adapter to write serialization to
|
||||
|
||||
## Return value
|
||||
|
||||
1. BON8 serialization as a byte vector
|
||||
2. (none)
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value `j`, which is never modified.
|
||||
With (2), the bytes written before the exception remain in the output adapter.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an unsigned integer
|
||||
above 9223372036854775807, which BON8 cannot represent
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
|
||||
valid UTF-8
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the JSON value `j`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example shows the serialization of a JSON value to a byte vector in BON8 format.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/to_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/to_bon8.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [from_bon8](from_bon8.md) create a JSON value from an input in BON8 format
|
||||
- [to_cbor](to_cbor.md) create a CBOR serialization of a JSON value
|
||||
- [to_msgpack](to_msgpack.md) create a MessagePack serialization of a JSON value
|
||||
- [to_bson](to_bson.md) create a BSON serialization of a JSON value
|
||||
- [to_ubjson](to_ubjson.md) create a UBJSON serialization of a JSON value
|
||||
- [to_bjdata](to_bjdata.md) create a BJData serialization of a JSON value
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -14,6 +14,11 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
- [**JSON_DIAGNOSTIC_POSITIONS**](json_diagnostic_positions.md) - access positions of elements
|
||||
- [**JSON_NOEXCEPTION**](json_noexception.md) - switch off exceptions
|
||||
|
||||
## Parsing
|
||||
|
||||
- [**JSON_STRICT_NUL_HANDLING**](json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of
|
||||
treating it as end of input
|
||||
|
||||
## Language support
|
||||
|
||||
- [**JSON_HAS_CPP_11**<br>**JSON_HAS_CPP_14**<br>**JSON_HAS_CPP_17**<br>**JSON_HAS_CPP_20**](json_has_cpp_11.md) - set supported C++ standard
|
||||
@@ -22,6 +27,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
- [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support
|
||||
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||
|
||||
@@ -38,6 +38,28 @@ The default value is `0` (disabled — existing behavior is preserved).
|
||||
|
||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
|
||||
|
||||
!!! warning "Applies to every single-element list"
|
||||
|
||||
The macro does not only affect a single JSON value in braces. **Any** single-element braced list is treated as its
|
||||
element, so it no longer creates a one-element array:
|
||||
|
||||
```cpp
|
||||
json j1 = {1}; // 1, not [1]
|
||||
json j2 = {"text"}; // "text", not ["text"]
|
||||
json j3 = {{1, 2}}; // [1,2], not [[1,2]]
|
||||
```
|
||||
|
||||
Code that relies on these producing arrays must use `json::array()` instead (see below). Lists with more than one
|
||||
element, and a single `[string, value]` pair such as `{{"key", "value"}}`, which still creates an object, are not
|
||||
affected. The library's own conversions are not affected either: for example, `std::tuple<int>{5}` still becomes
|
||||
`[5]`.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_bics`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
To explicitly create a single-element array without enabling this macro, use `json::array()`:
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# JSON_NO_THREAD_LOCAL
|
||||
|
||||
```cpp
|
||||
#define JSON_NO_THREAD_LOCAL
|
||||
```
|
||||
|
||||
When defined, the library does not use `#!cpp thread_local` storage. This is relevant for the few environments whose
|
||||
toolchain does not support it.
|
||||
|
||||
The copy constructor copies the first levels of a value by copying the containers, which copy their elements, and
|
||||
completes whatever is nested deeper than that without the call stack, so that copying a value cannot 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
|
||||
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
|
||||
away. Copying keeps working exactly as it does otherwise - the same values come out, and deeply nested values are copied
|
||||
just as safely - but copying is slower, because the containers no longer copy themselves. Copying the benchmark
|
||||
documents takes 9% (`canada.json`) to 34% (`twitter.json`) longer; values built mostly from objects are affected the
|
||||
most.
|
||||
|
||||
## Default definition
|
||||
|
||||
By default, `#!cpp JSON_NO_THREAD_LOCAL` is not defined.
|
||||
|
||||
```cpp
|
||||
#undef JSON_NO_THREAD_LOCAL
|
||||
```
|
||||
|
||||
The library defines it by itself for Clang targeting MinGW, which does not survive the `#!cpp thread_local` storage:
|
||||
copying a value segfaults there, with both old and current Clang versions, while GCC targeting MinGW is unaffected.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The code below forces the library not to use `#!cpp thread_local` storage.
|
||||
|
||||
```cpp
|
||||
#define JSON_NO_THREAD_LOCAL 1
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.12.1.
|
||||
@@ -0,0 +1,126 @@
|
||||
# JSON_STRICT_NUL_HANDLING
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, a `'\0'` (NUL) byte in JSON text input is rejected with `parse_error.101`, like any other
|
||||
unexpected byte, instead of being silently treated as end of input.
|
||||
|
||||
The macro only affects the JSON text parser ([`parse`](../basic_json/parse.md), [`accept`](../basic_json/accept.md),
|
||||
[`sax_parse`](../basic_json/sax_parse.md), and [`operator>>`](../operator_gtgt.md)). There are three cases where a NUL
|
||||
byte is still not rejected:
|
||||
|
||||
- The binary formats ([`from_bjdata`](../basic_json/from_bjdata.md), [`from_bon8`](../basic_json/from_bon8.md),
|
||||
[`from_bson`](../basic_json/from_bson.md), [`from_cbor`](../basic_json/from_cbor.md),
|
||||
[`from_msgpack`](../basic_json/from_msgpack.md), [`from_ubjson`](../basic_json/from_ubjson.md)) are never affected: there, `0x00` is ordinary data.
|
||||
- A bare `const char*` pointer has no length of its own, so its length is still determined with `strlen()`. The first
|
||||
NUL byte therefore still marks the end of the input, and nothing after it is read.
|
||||
- One trailing `'\0'` at the end of a `char` array (e.g., a string literal) is trimmed; see the warning below.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0` (disabled — existing behavior is preserved).
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Background"
|
||||
|
||||
By default, a `'\0'` byte anywhere in the input is treated the same as the real end of the input, rather than as
|
||||
an ordinary (and, outside of a string, invalid) byte. Everything from that byte onward is silently ignored,
|
||||
without a parse error - including further, otherwise well-formed JSON:
|
||||
|
||||
```cpp
|
||||
json::parse(std::string("123") + '\0'); // == 123, no error
|
||||
json::parse(std::string("123") + '\0' + "true"); // == 123, the "true" is silently ignored too
|
||||
```
|
||||
|
||||
This falls out of the same convention used when no explicit input length is given at all: parsing from a
|
||||
`const char*` already stops at the first NUL byte via `strlen()`, since a bare pointer has no length of its own.
|
||||
The library applies that same NUL-terminated-C-string convention uniformly, rather than only when a length is
|
||||
genuinely unavailable - so a `std::string`, iterator range, or container whose content happens to include a NUL
|
||||
byte is affected the same way a raw `const char*` would be (see the
|
||||
[FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for a fuller explanation).
|
||||
|
||||
This was not fixed unconditionally, because doing so is backwards-incompatible for any caller who happens to
|
||||
depend on the current behavior - even unknowingly, for instance because their input already contains trailing
|
||||
padding they never noticed was being discarded (see [#5530](https://github.com/nlohmann/json/issues/5530)).
|
||||
This macro instead offers an opt-in path to the corrected behavior ahead of version 4.0.0, where it is planned to
|
||||
become the default.
|
||||
|
||||
!!! warning "Opt-in only"
|
||||
|
||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no
|
||||
effect.
|
||||
|
||||
Enabling it also changes how a `char` array (including a string literal, e.g. `json::parse("123")`) is read: such
|
||||
an array normally carries a trailing `'\0'` contributed by the compiler, not by the source text. With this macro
|
||||
enabled, that one trailing byte is trimmed if present so that parsing a string literal keeps working; every other
|
||||
byte in the array - including any `'\0'` that is not the very last element - is read as real data and rejected
|
||||
like any other unexpected byte. Arrays of any other element type (`unsigned char`, `std::uint8_t`, ...), as used
|
||||
for CBOR or MessagePack, are never affected by this trimming; their full extent - including a genuine trailing
|
||||
`0x00` - is always preserved, in both states of this macro.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
To reject a NUL byte without enabling this macro, trim your input yourself before calling `parse()`:
|
||||
|
||||
```cpp
|
||||
s.resize(s.find('\0')); // drop everything from the first NUL onward, if any
|
||||
json::parse(s);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Default behavior (macro not defined)"
|
||||
|
||||
Without the macro, a NUL byte silently ends parsing at that point:
|
||||
|
||||
```cpp
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
json j = json::parse(std::string("123") + '\0' + "true");
|
||||
// j is 123 -- the '\0' and everything after it is silently ignored
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Opt-in strict handling (macro defined to 1)"
|
||||
|
||||
With the macro, a NUL byte is rejected like any other unexpected byte:
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING 1
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
json j = json::parse(std::string("123") + '\0' + "true");
|
||||
// throws parse_error.101 -- the NUL byte is now invalid input,
|
||||
// exactly like any other unexpected trailing byte
|
||||
|
||||
json ok = json::parse("123");
|
||||
// ok is 123 -- parsing from a string literal still works
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [FAQ: NUL bytes in the input](../../home/faq.md#nul-bytes-in-the-input)
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**accept**](../basic_json/accept.md) - check if the input is valid JSON
|
||||
- [**operator>>**](../operator_gtgt.md) - deserialize from stream
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
- Planned to become the default (with the macro removed) in version 4.0.0.
|
||||
@@ -24,6 +24,14 @@ By default, implicit conversions are enabled.
|
||||
You can prepare existing code by already defining `JSON_USE_IMPLICIT_CONVERSIONS` to `0` and replace any implicit
|
||||
conversions with calls to [`get`](../basic_json/get.md).
|
||||
|
||||
!!! tip "Automatic migration"
|
||||
|
||||
The community-maintained clang-tidy check `modernize-nlohmann-json-explicit-conversions` rewrites implicit
|
||||
conversions into explicit calls to [`get`](../basic_json/get.md); for example, `#!cpp int i = j;` becomes
|
||||
`#!cpp int i = j.get<int>();`. The check is not part of clang-tidy itself, and it does not catch every case (for
|
||||
example, constructing a `std::optional` from a JSON value), so review the result. See
|
||||
[discussion #4610](https://github.com/nlohmann/json/discussions/4610) for how to build and use it.
|
||||
|
||||
!!! hint "CMake option"
|
||||
|
||||
Implicit conversions can also be controlled with the CMake option
|
||||
|
||||
@@ -72,6 +72,13 @@ input >> j2; // parses the next value
|
||||
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
||||
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
||||
|
||||
By default, a `'\0'` (NUL) byte encountered while reading a value is treated as end of input, rather than as an
|
||||
ordinary (and, outside of a string, invalid) byte; see the [FAQ entry](../home/faq.md#nul-bytes-in-the-input) for
|
||||
details and the [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) macro to opt into rejecting it
|
||||
instead. Because `operator>>` only parses a single value and does not require the rest of the stream to be consumed,
|
||||
a NUL byte *after* a complete value has no effect on `operator>>` either way; it only matters while a value is still
|
||||
being read.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
This function replaces function `#!cpp std::istream& operator<<(basic_json& j, std::istream& i)` which has
|
||||
@@ -98,7 +105,11 @@ Note that reading concatenated values does **not** work for [JSON Lines](../feat
|
||||
|
||||
- [accept](basic_json/accept.md) - check if the input is valid JSON
|
||||
- [parse](basic_json/parse.md) - deserialize from a compatible input
|
||||
- [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create byte vector
|
||||
std::vector<std::uint8_t> v = {0x89, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74,
|
||||
0xf9, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0xff,
|
||||
0x42, 0x4f, 0x4e, 0x38, 0xff, 0x73, 0x63, 0x68,
|
||||
0x65, 0x6d, 0x61, 0x90
|
||||
};
|
||||
|
||||
// deserialize it with BON8
|
||||
json j = json::from_bon8(v);
|
||||
|
||||
// print the deserialized JSON value
|
||||
std::cout << std::setw(2) << j << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compact": true,
|
||||
"format": "BON8",
|
||||
"schema": 0
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using namespace nlohmann::literals;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create a JSON value
|
||||
json j = R"({"compact": true, "format": "BON8", "schema": 0})"_json;
|
||||
|
||||
// serialize it to BON8
|
||||
std::vector<std::uint8_t> v = json::to_bon8(j);
|
||||
|
||||
// print the vector content
|
||||
for (auto& byte : v)
|
||||
{
|
||||
std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0x89 0x63 0x6f 0x6d 0x70 0x61 0x63 0x74 0xf9 0x66 0x6f 0x72 0x6d 0x61 0x74 0xff 0x42 0x4f 0x4e 0x38 0xff 0x73 0x63 0x68 0x65 0x6d 0x61 0x90
|
||||
@@ -116,18 +116,22 @@ The library uses the following mapping from JSON values types to BJData types ac
|
||||
```
|
||||
|
||||
Likewise, when a JSON object in the above form is serialized using
|
||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. When
|
||||
the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two integers with one being 1, a
|
||||
regular 1-D optimized array is generated instead.
|
||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array.
|
||||
|
||||
An object is only converted if the annotation actually describes a packed array; otherwise it is serialized as a
|
||||
regular JSON object. This requires all of the following:
|
||||
When parsing, an ND-array whose dimension vector is empty, contains a single integer, contains two integers with the
|
||||
first being 1, or contains a 0 is returned as a regular (possibly empty) array rather than an annotated object.
|
||||
|
||||
An object is only converted if the annotation describes a packed array that is parsed back into the same annotated
|
||||
object; otherwise it is serialized as a regular JSON object, so the annotation is never lost in a round trip. This requires
|
||||
all of the following:
|
||||
|
||||
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
|
||||
`double`, `char`, or `byte`,
|
||||
- `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length,
|
||||
- every entry of `"_ArraySize_"` is a non-negative integer, and their product is representable as a `std::size_t`,
|
||||
- `"_ArrayData_"` holds exactly that many elements, and
|
||||
- `"_ArraySize_"` has at least two entries and is not a 1×N row vector (first entry 1), since other shapes are
|
||||
parsed back as a regular array,
|
||||
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
||||
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
||||
`single` and `double`, an integer otherwise).
|
||||
|
||||
@@ -204,14 +208,6 @@ 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.
|
||||
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
BJData does not specify an encoding for its `string`/`char` types, but this library requires them to be valid
|
||||
UTF-8, consistent with the rest of the library. The bytes of every such string (object keys included) are
|
||||
validated at decode time, and ill-formed UTF-8 is rejected 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.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# BON8
|
||||
|
||||
BON8 (Binary Object Notation 8) is a compact binary serialization format for JSON values. It uses the byte values that
|
||||
cannot begin a UTF-8 character as type markers, so strings are stored as plain UTF-8 without a length prefix: a string
|
||||
ends at the first byte that cannot continue it. Integers from -10 to 39, `true`, `false`, `null`, and the floating-point
|
||||
values -1.0, 0.0, and 1.0 take a single byte, and arrays and objects with up to four elements need no terminator.
|
||||
|
||||
!!! abstract "References"
|
||||
|
||||
- [BON8 specification](https://github.com/hikoworks/hikogui/blob/main/docs/BON8.md)
|
||||
- [Reference implementation](https://github.com/hikoworks/hikogui/blob/main/src/hikogui/codec/BON8.hpp) in HikoGUI
|
||||
|
||||
## Serialization
|
||||
|
||||
The library uses the following mapping from JSON values types to BON8 types according to the BON8 specification:
|
||||
|
||||
| JSON value type | value/range | BON8 type | first byte |
|
||||
|-----------------|----------------------------------------------|-------------------------------|------------|
|
||||
| null | `null` | null | 0xFA |
|
||||
| boolean | `true` | true | 0xF9 |
|
||||
| boolean | `false` | false | 0xF8 |
|
||||
| number_integer | -9223372036854775808..-2147483649 | int64 | 0x8D |
|
||||
| number_integer | -2147483648..-33818507 | int32 | 0x8C |
|
||||
| number_integer | -33818506..-264075 | 4-byte negative integer | 0xF0..0xF7 |
|
||||
| number_integer | -264074..-1931 | 3-byte negative integer | 0xE0..0xEF |
|
||||
| number_integer | -1930..-11 | 2-byte negative integer | 0xC2..0xDF |
|
||||
| number_integer | -10..-1 | 1-byte negative integer | 0xB8..0xC1 |
|
||||
| number_integer | 0..39 | 1-byte positive integer | 0x90..0xB7 |
|
||||
| number_integer | 40..3879 | 2-byte positive integer | 0xC2..0xDF |
|
||||
| number_integer | 3880..528167 | 3-byte positive integer | 0xE0..0xEF |
|
||||
| number_integer | 528168..67637031 | 4-byte positive integer | 0xF0..0xF7 |
|
||||
| number_integer | 67637032..2147483647 | int32 | 0x8C |
|
||||
| number_integer | 2147483648..9223372036854775807 | int64 | 0x8D |
|
||||
| number_unsigned | 0..39 | 1-byte positive integer | 0x90..0xB7 |
|
||||
| number_unsigned | 40..3879 | 2-byte positive integer | 0xC2..0xDF |
|
||||
| number_unsigned | 3880..528167 | 3-byte positive integer | 0xE0..0xEF |
|
||||
| number_unsigned | 528168..67637031 | 4-byte positive integer | 0xF0..0xF7 |
|
||||
| number_unsigned | 67637032..2147483647 | int32 | 0x8C |
|
||||
| number_unsigned | 2147483648..9223372036854775807 | int64 | 0x8D |
|
||||
| number_float | `-1.0` | -1.0 | 0xFB |
|
||||
| number_float | `0.0` | 0.0 | 0xFC |
|
||||
| number_float | `1.0` | 1.0 | 0xFD |
|
||||
| number_float | *any other value representable by a float* | binary32 | 0x8E |
|
||||
| number_float | *any value NOT representable by a float* | binary64 | 0x8F |
|
||||
| string | *empty* | end of string | 0xFF |
|
||||
| string | *non-empty* | UTF-8 string | 0x00..0x7F, 0xC2..0xF4 |
|
||||
| array | *size*: 0..4 | array with count | 0x80..0x84 |
|
||||
| array | *size*: 5 or more | array (terminated by 0xFE) | 0x85 |
|
||||
| object | *size*: 0..4 | object with count | 0x86..0x8A |
|
||||
| object | *size*: 5 or more | object (terminated by 0xFE) | 0x8B |
|
||||
| binary | *size*: 0..4 | array with count | 0x80..0x84 |
|
||||
| binary | *size*: 5 or more | array (terminated by 0xFE) | 0x85 |
|
||||
|
||||
An integer that takes 2 to 4 bytes starts with a UTF-8 lead byte (0xC2..0xF7) that is followed by a byte that cannot
|
||||
continue a UTF-8 character: 0x00..0x7F for positive and 0xC0..0xFF for negative integers. A string is terminated by
|
||||
0xFF only if it is empty, if another string follows it, or if it is the last value of the message; otherwise, the first
|
||||
byte of the next value ends it.
|
||||
|
||||
!!! success "Complete mapping"
|
||||
|
||||
Except for the values listed below, any JSON value can be converted to a BON8 value.
|
||||
|
||||
Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`.
|
||||
|
||||
!!! warning "Unsupported values"
|
||||
|
||||
The following values can **not** be converted to a BON8 value:
|
||||
|
||||
- unsigned integers above 9223372036854775807, because BON8 has no unsigned 64-bit integer type
|
||||
([out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407))
|
||||
- strings that are not valid UTF-8, because the end of a string is determined from its encoding
|
||||
([type_error.316](../../home/exceptions.md#jsonexceptiontype_error316))
|
||||
|
||||
!!! info "NaN/infinity handling"
|
||||
|
||||
`-0.0`, `Infinity`, and `-Infinity` are serialized as binary32 (type 0x8E, 5 bytes total). `NaN` is serialized as
|
||||
the binary32 value 0x7F800001 that the specification recommends. This is in contrast to the
|
||||
[dump](../../api/basic_json/dump.md) function which serializes NaN or Infinity to `null`.
|
||||
|
||||
!!! warning "Binary values"
|
||||
|
||||
BON8 has no binary type. Binary values are serialized as arrays of integers (0..255), so they are read back as
|
||||
arrays. The subtype is not serialized.
|
||||
|
||||
!!! info "Canonical representation"
|
||||
|
||||
The output follows the specification's canonical representation rules: every value uses the shortest encoding,
|
||||
floating-point numbers use binary32 whenever that loses no precision, and object keys are sorted by their UTF-8
|
||||
code units. There are two exceptions:
|
||||
|
||||
- Strings are not normalized to Unicode Normalization Form C (NFC).
|
||||
- Object keys are written in the order of the object type, which is sorted for `json`, but not for
|
||||
[`ordered_json`](../../api/ordered_json.md).
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/to_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```c
|
||||
--8<-- "examples/to_bon8.output"
|
||||
```
|
||||
|
||||
## Deserialization
|
||||
|
||||
The library maps BON8 types to JSON value types as follows:
|
||||
|
||||
| BON8 type | JSON value type | first byte |
|
||||
|-------------------------------|-----------------|------------------------|
|
||||
| UTF-8 string | string | 0x00..0x7F |
|
||||
| array with count | array | 0x80..0x84 |
|
||||
| array (terminated by 0xFE) | array | 0x85 |
|
||||
| object with count | object | 0x86..0x8A |
|
||||
| object (terminated by 0xFE) | object | 0x8B |
|
||||
| int32 | number_unsigned or number_integer | 0x8C |
|
||||
| int64 | number_unsigned or number_integer | 0x8D |
|
||||
| binary32 | number_float | 0x8E |
|
||||
| binary64 | number_float | 0x8F |
|
||||
| 1-byte positive integer | number_unsigned | 0x90..0xB7 |
|
||||
| 1-byte negative integer | number_integer | 0xB8..0xC1 |
|
||||
| UTF-8 string | string | 0xC2..0xF4, followed by 0x80..0xBF |
|
||||
| 2- to 4-byte positive integer | number_unsigned | 0xC2..0xF7, followed by 0x00..0x7F |
|
||||
| 2- to 4-byte negative integer | number_integer | 0xC2..0xF7, followed by 0xC0..0xFF |
|
||||
| false | `false` | 0xF8 |
|
||||
| true | `true` | 0xF9 |
|
||||
| null | `null` | 0xFA |
|
||||
| -1.0 | number_float | 0xFB |
|
||||
| 0.0 | number_float | 0xFC |
|
||||
| 1.0 | number_float | 0xFD |
|
||||
| empty string | string | 0xFF |
|
||||
|
||||
Non-negative integers are read as number_unsigned, negative integers as number_integer.
|
||||
|
||||
!!! info
|
||||
|
||||
Values that do not use the canonical representation, such as integers with a longer encoding than necessary,
|
||||
arrays and objects with up to four elements that are terminated by 0xFE, unsorted object keys, or unneeded 0xFF
|
||||
bytes after a string, are accepted.
|
||||
|
||||
Strings must be valid UTF-8, and the last string of a message must be terminated by 0xFF.
|
||||
|
||||
!!! info
|
||||
|
||||
Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/from_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/from_bon8.output"
|
||||
```
|
||||
@@ -109,15 +109,6 @@ The library maps BSON record types to JSON value types as follows:
|
||||
If BSON input must be validated for strict specification compliance, validate it separately before passing it to
|
||||
`from_bson()`.
|
||||
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
The BSON specification requires `string` values (type `0x02`) to be valid UTF-8. This library validates the
|
||||
bytes of every such string at decode time and rejects ill-formed UTF-8 with a
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions`
|
||||
set to `false`, a discarded value), rather than only failing later when the resulting value is dumped. Element
|
||||
(key) names and `binary` values (type `0x05`) are unaffected and are never validated, since they are read
|
||||
byte-by-byte as a C string, or are not required to hold text, respectively.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -176,16 +176,6 @@ The library maps CBOR types to JSON value types as follows:
|
||||
|
||||
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected.
|
||||
|
||||
!!! warning "UTF-8 validation of text strings"
|
||||
|
||||
[RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings
|
||||
(major type 3) to be valid UTF-8. This library validates the bytes of every text string (object keys included) at
|
||||
decode time and rejects ill-formed UTF-8 with a
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value is
|
||||
dumped. Byte strings (major type 2) are unaffected and are never validated, since they are not required to hold
|
||||
text.
|
||||
|
||||
!!! warning "Tagged items"
|
||||
|
||||
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||
|
||||
@@ -4,6 +4,7 @@ Though JSON is a ubiquitous data format, it is not a very compact format suitabl
|
||||
a network. Hence, the library supports
|
||||
|
||||
- [BJData](bjdata.md) (Binary JData),
|
||||
- [BON8](bon8.md) (Binary Object Notation 8),
|
||||
- [BSON](bson.md) (Binary JSON),
|
||||
- [CBOR](cbor.md) (Concise Binary Object Representation),
|
||||
- [MessagePack](messagepack.md), and
|
||||
@@ -18,6 +19,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
| Format | Serialization | Deserialization |
|
||||
|-------------|-----------------------------------------------|----------------------------------------------|
|
||||
| BJData | complete | complete |
|
||||
| BON8 | incomplete: no unsigned integers above int64 | complete |
|
||||
| BSON | incomplete: top-level value must be an object | incomplete, but all JSON types are supported |
|
||||
| CBOR | complete | incomplete, but all JSON types are supported |
|
||||
| MessagePack | complete | complete |
|
||||
@@ -28,6 +30,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
| Format | Binary values | Binary subtypes |
|
||||
|-------------|---------------|-----------------|
|
||||
| BJData | not supported | not supported |
|
||||
| BON8 | not supported | not supported |
|
||||
| BSON | supported | supported |
|
||||
| CBOR | supported | supported |
|
||||
| MessagePack | supported | supported |
|
||||
@@ -42,6 +45,7 @@ See [binary values](../binary_values.md) for more information.
|
||||
| BJData | 53.2 % | 91.1 % | 78.1 % | 96.6 % |
|
||||
| BJData (size) | 58.6 % | 92.1 % | 86.7 % | 97.4 % |
|
||||
| BJData (size+type) | 58.6 % | 92.1 % | 86.5 % | 97.4 % |
|
||||
| BON8 | 50.5 % | 83.8 % | 63.5 % | 87.5 % |
|
||||
| BSON | 85.8 % | 95.2 % | 95.8 % | 106.7 % |
|
||||
| CBOR | 50.5 % | 86.3 % | 68.4 % | 88.0 % |
|
||||
| MessagePack | 50.5 % | 86.0 % | 68.5 % | 87.9 % |
|
||||
|
||||
@@ -136,14 +136,6 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
|
||||
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
||||
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
||||
This library validates the bytes of every such string (object keys included) at decode time and rejects
|
||||
ill-formed UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or,
|
||||
with `allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting
|
||||
value is dumped. `bin`/`ext`/`fixext` values are unaffected and are never validated, since they are not required
|
||||
to hold text.
|
||||
|
||||
??? example
|
||||
|
||||
|
||||
@@ -120,14 +120,6 @@ The library maps UBJSON types to JSON value types as follows:
|
||||
|
||||
The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value.
|
||||
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
UBJSON does not specify an encoding for its `string`/`char` types, but this library requires them to be valid
|
||||
UTF-8, consistent with the rest of the library. The bytes of every such string (object keys included) are
|
||||
validated at decode time, and ill-formed UTF-8 is rejected 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.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -187,6 +187,41 @@ as an array of uint8 values. The library implements this translation.
|
||||
}
|
||||
```
|
||||
|
||||
### BON8
|
||||
|
||||
[BON8](binary_formats/bon8.md) neither supports binary values nor subtypes. The library serializes binary values as an
|
||||
array of integers.
|
||||
|
||||
??? example
|
||||
|
||||
Code:
|
||||
|
||||
```cpp
|
||||
// create a binary value of subtype 42 (will be ignored in BON8)
|
||||
json j;
|
||||
j["binary"] = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 42);
|
||||
|
||||
// convert to BON8
|
||||
auto v = json::to_bon8(j);
|
||||
```
|
||||
|
||||
`v` is a `std::vector<std::uint8_t>` with the following 16 elements:
|
||||
|
||||
```c
|
||||
0x87 // object with 1 member
|
||||
0x62 0x69 0x6E 0x61 0x72 0x79 // "binary"
|
||||
0x84 // array with 4 elements
|
||||
0xC3 0x22 0xC3 0x56 0xC3 0x12 0xC3 0x16 // content (each byte as a 2-byte integer)
|
||||
```
|
||||
|
||||
Note that the subtype is lost, and deserializing `v` would yield the following value:
|
||||
|
||||
```json
|
||||
{
|
||||
"binary": [202, 254, 186, 190]
|
||||
}
|
||||
```
|
||||
|
||||
### BSON
|
||||
|
||||
[BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as an
|
||||
|
||||
@@ -35,8 +35,8 @@ C++ types, and finally serialize it again.
|
||||
- [Serialization](serialization.md) — turn a value back into JSON text with [`dump`](../api/basic_json/dump.md),
|
||||
including pretty-printing and handling of non-ASCII and invalid UTF-8.
|
||||
- [Binary formats](binary_formats/index.md) — encode values more compactly as
|
||||
[BJData](binary_formats/bjdata.md), [BSON](binary_formats/bson.md), [CBOR](binary_formats/cbor.md),
|
||||
[MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
|
||||
[BJData](binary_formats/bjdata.md), [BON8](binary_formats/bon8.md), [BSON](binary_formats/bson.md),
|
||||
[CBOR](binary_formats/cbor.md), [MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
|
||||
- [Binary values](binary_values.md) — store and exchange raw byte sequences.
|
||||
|
||||
## How values are stored and configured
|
||||
|
||||
@@ -91,6 +91,13 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
|
||||
|
||||
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
||||
|
||||
## `JSON_NO_THREAD_LOCAL`
|
||||
|
||||
When defined, the library does not use `#!cpp thread_local` storage. Copying a value then always avoids the call stack
|
||||
rather than descending into a bounded number of levels first, which is slower but yields the same values.
|
||||
|
||||
See [full documentation of `JSON_NO_THREAD_LOCAL`](../api/macros/json_no_thread_local.md).
|
||||
|
||||
## `JSON_SKIP_LIBRARY_VERSION_CHECK`
|
||||
|
||||
When defined, the library will not create a compiler warning when a different version of the library was already
|
||||
@@ -105,6 +112,19 @@ using the library with compilers that do not fully support C++11 and may only wo
|
||||
|
||||
See [full documentation of `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`](../api/macros/json_skip_unsupported_compiler_check.md).
|
||||
|
||||
## `JSON_STRICT_NUL_HANDLING`
|
||||
|
||||
When defined to `1`, a `'\0'` (NUL) byte anywhere in the input is rejected with `parse_error.101`, like any other
|
||||
unexpected byte, instead of being silently treated as end of input (see the
|
||||
[FAQ entry](../home/faq.md#nul-bytes-in-the-input) for background). The default value is `0`, which preserves the
|
||||
existing behavior; this is planned to become the default in version 4.0.0.
|
||||
|
||||
The strict handling can also be enabled with the CMake option
|
||||
[`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) (`OFF` by default) which sets
|
||||
`JSON_STRICT_NUL_HANDLING` accordingly.
|
||||
|
||||
See [full documentation of `JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md).
|
||||
|
||||
## `JSON_THROW_USER(exception)`
|
||||
|
||||
This macro overrides `#!cpp throw` calls inside the library. The argument is the exception to be thrown.
|
||||
|
||||
@@ -15,6 +15,9 @@ The complete default namespace name is derived as follows:
|
||||
- [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) defined non-zero appends `_diag`.
|
||||
- [`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md)
|
||||
defined non-zero appends `_ldvcmp`.
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) defined non-zero appends `_dp`.
|
||||
- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) defined non-zero appends
|
||||
`_bics`.
|
||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
||||
below.
|
||||
|
||||
@@ -117,7 +117,7 @@ For the [{fmt}](https://github.com/fmtlib/fmt) library, the library ships a
|
||||
## Serializing to other formats
|
||||
|
||||
Besides JSON text, a value can also be serialized to the more compact [binary formats](binary_formats/index.md)
|
||||
(BJData, BSON, CBOR, MessagePack, UBJSON).
|
||||
(BJData, BON8, BSON, CBOR, MessagePack, UBJSON).
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -547,7 +547,7 @@ Grisu2 algorithm, which produces the shortest representation that round-trips. O
|
||||
### Required for the binary formats
|
||||
|
||||
`NumberFloatType` must be `#!cpp float` or `#!cpp double`. The writers for
|
||||
[CBOR, MessagePack, UBJSON, BJData, and BSON](../binary_formats/index.md) map a floating-point value onto an IEEE 754
|
||||
[CBOR, MessagePack, UBJSON, BJData, BON8, and BSON](../binary_formats/index.md) map a floating-point value onto an IEEE 754
|
||||
binary32 or binary64 field and have no encoding for `#!cpp long double`.
|
||||
|
||||
### Compatible types
|
||||
|
||||
@@ -340,8 +340,7 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
|
||||
### json.exception.parse_error.113
|
||||
|
||||
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
|
||||
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
||||
the string's bytes are not valid UTF-8.
|
||||
string was read where one was required (for instance as a map key), or the string's length specification is invalid.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
@@ -357,9 +356,6 @@ the string's bytes are not valid UTF-8.
|
||||
```
|
||||
[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
|
||||
|
||||
@@ -974,6 +970,21 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa
|
||||
|
||||
This exception was added in version 3.13.0. Before that, this situation could succeed with a corrupted result: for an array target, removing the "from" element before the "add" step shifted subsequent indices, so "path" silently re-resolved to a different element than intended.
|
||||
|
||||
### json.exception.out_of_range.415
|
||||
|
||||
MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a
|
||||
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
[json.exception.out_of_range.415] subtype 70000 is too large for the MessagePack ext type (max 255)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0. Before that, subtypes above 255 were silently truncated modulo 256 instead of raising an error.
|
||||
|
||||
## Further exceptions
|
||||
|
||||
This exception is thrown in case of errors that cannot be classified with the
|
||||
|
||||
@@ -90,6 +90,54 @@ The library supports **Unicode input** as follows:
|
||||
In most cases, the parser is right to complain, because the input is not UTF-8 encoded. This is especially true for Microsoft Windows, where Latin-1 or ISO 8859-1 is often the standard encoding.
|
||||
|
||||
|
||||
### NUL bytes in the input
|
||||
|
||||
!!! question "Questions"
|
||||
|
||||
- Why does `json::parse()` silently ignore part of my input?
|
||||
- Why does a `std::string`/buffer with extra data after the JSON text parse without error, while a similar-looking string with extra text does not?
|
||||
|
||||
A `'\0'` (NUL) byte anywhere in the input is treated the same as the real end of the input, rather than as an ordinary (and, outside of a string, invalid) byte. Everything from that byte onward is silently ignored, without a parse error — including further, otherwise well-formed JSON:
|
||||
|
||||
```cpp
|
||||
json::parse(std::string("123") + '\0'); // == 123, no error
|
||||
json::parse(std::string("123") + '\0' + "true"); // == 123, the "true" is silently ignored too
|
||||
```
|
||||
|
||||
This is different from any other unexpected trailing byte, which *does* raise [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101):
|
||||
|
||||
```cpp
|
||||
json::parse("123x"); // throws parse_error.101: unexpected additional data
|
||||
```
|
||||
|
||||
This falls out of the same convention used when no explicit input length is given at all: `json::parse(const char*)` already stops at the first NUL byte via `strlen()`, since a bare pointer has no length of its own. The library applies that same NUL-terminated-C-string convention uniformly, rather than only when a length is genuinely unavailable — so a `std::string`, iterator range, or container whose content happens to include a NUL byte is affected the same way a raw `const char*` would be.
|
||||
|
||||
If your input may contain a trailing or embedded NUL that is **not** meant to signal the end of the JSON text — for instance, a fixed-size, zero-padded buffer — trim it yourself before calling `parse()`, since the library will otherwise silently stop there instead of raising an error:
|
||||
|
||||
```cpp
|
||||
s.resize(s.find('\0')); // drop everything from the first NUL onward, if any
|
||||
json::parse(s);
|
||||
```
|
||||
|
||||
**Opt-in strict handling (since version 3.13.0)**
|
||||
|
||||
Manually trimming every input is easy to forget. If you define [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) to `1` before including the library, a `'\0'` byte is instead rejected like any other unexpected byte and raises `parse_error.101`, instead of being treated as end of input:
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING 1
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
json::parse(std::string("123") + '\0'); // throws parse_error.101 instead of silently returning 123
|
||||
```
|
||||
|
||||
This macro defaults to `0` (disabled, preserving the behavior described above) to avoid breaking existing code that may depend on it, even unknowingly; it is planned to become the default in version 4.0.0. See [its documentation](../api/macros/json_strict_nul_handling.md) for details, including how it also affects `char` arrays such as string literals.
|
||||
|
||||
Note that this is unrelated to an *unescaped* NUL byte occurring **inside** a quoted JSON string, which is a different, already-invalid case and is correctly rejected either way:
|
||||
|
||||
```cpp
|
||||
json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control character U+0000 (NUL) must be escaped to \u0000
|
||||
```
|
||||
|
||||
### Wide string handling
|
||||
|
||||
!!! question
|
||||
|
||||
@@ -198,6 +198,11 @@ Use the non-amalgamated version of the library. This option is `ON` by default.
|
||||
|
||||
Treat the library headers like system headers (i.e., adding `SYSTEM` to the [`target_include_directories`](https://cmake.org/cmake/help/latest/command/target_include_directories.html) call) to check for this library by tools like Clang-Tidy. This option is `OFF` by default.
|
||||
|
||||
### `JSON_StrictNulHandling`
|
||||
|
||||
Reject a `'\0'` (NUL) byte in the input instead of treating it as end of input, by defining the macro
|
||||
[`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md). This option is `OFF` by default.
|
||||
|
||||
### `JSON_Valgrind`
|
||||
|
||||
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||
|
||||
@@ -176,6 +176,12 @@ You can prepare existing code by already defining
|
||||
conversions with calls to [`get`](../api/basic_json/get.md), [`get_to`](../api/basic_json/get_to.md),
|
||||
[`get_ref`](../api/basic_json/get_ref.md), or [`get_ptr`](../api/basic_json/get_ptr.md).
|
||||
|
||||
!!! tip "Automatic migration"
|
||||
|
||||
The community-maintained clang-tidy check `modernize-nlohmann-json-explicit-conversions` rewrites most implicit
|
||||
conversions into calls to [`get`](../api/basic_json/get.md). It is not part of clang-tidy itself; see
|
||||
[discussion #4610](https://github.com/nlohmann/json/discussions/4610) for how to build and use it.
|
||||
|
||||
=== "Deprecated"
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*[ASCII]: American Standard Code for Information Interchange
|
||||
*[BDFL]: Benevolent Dictator for Life
|
||||
*[BJData]: Binary JData
|
||||
*[BON8]: Binary Object Notation 8
|
||||
*[BSON]: Binary JSON
|
||||
*[CBOR]: Concise Binary Object Representation
|
||||
*[CC0]: Creative Commons Zero
|
||||
|
||||
@@ -63,6 +63,7 @@ nav:
|
||||
- Binary Formats:
|
||||
- features/binary_formats/index.md
|
||||
- features/binary_formats/bjdata.md
|
||||
- features/binary_formats/bon8.md
|
||||
- features/binary_formats/bson.md
|
||||
- features/binary_formats/cbor.md
|
||||
- features/binary_formats/messagepack.md
|
||||
@@ -142,6 +143,7 @@ nav:
|
||||
- 'flatten': api/basic_json/flatten.md
|
||||
- 'format_as': api/basic_json/format_as.md
|
||||
- 'from_bjdata': api/basic_json/from_bjdata.md
|
||||
- 'from_bon8': api/basic_json/from_bon8.md
|
||||
- 'from_bson': api/basic_json/from_bson.md
|
||||
- 'from_cbor': api/basic_json/from_cbor.md
|
||||
- 'from_msgpack': api/basic_json/from_msgpack.md
|
||||
@@ -213,6 +215,7 @@ nav:
|
||||
- 'swap': api/basic_json/swap.md
|
||||
- 'std::swap<basic_json>': api/basic_json/std_swap.md
|
||||
- 'to_bjdata': api/basic_json/to_bjdata.md
|
||||
- 'to_bon8': api/basic_json/to_bon8.md
|
||||
- 'to_bson': api/basic_json/to_bson.md
|
||||
- 'to_cbor': api/basic_json/to_cbor.md
|
||||
- 'to_msgpack': api/basic_json/to_msgpack.md
|
||||
@@ -292,8 +295,10 @@ nav:
|
||||
- 'JSON_HAS_THREE_WAY_COMPARISON': api/macros/json_has_three_way_comparison.md
|
||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
||||
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
||||
- 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md
|
||||
- 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md
|
||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
||||
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
||||
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
||||
@@ -407,7 +412,7 @@ plugins:
|
||||
markdown_description: >
|
||||
JSON for Modern C++ is a C++11 header-only library implementing a JSON
|
||||
value type with an STL-like API, JSON Pointer/Patch, CBOR/MessagePack/
|
||||
BSON/UBJSON/BJData binary format support, and a SAX-style parser interface.
|
||||
BSON/UBJSON/BJData/BON8 binary format support, and a SAX-style parser interface.
|
||||
sections:
|
||||
Home:
|
||||
- index.md
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
wheel==0.48.0
|
||||
|
||||
mkdocs==1.6.1 # documentation framework
|
||||
mkdocs-git-revision-date-localized-plugin==1.5.4 # plugin "git-revision-date-localized"
|
||||
mkdocs-git-revision-date-localized-plugin==1.6.0 # plugin "git-revision-date-localized"
|
||||
mkdocs-material==9.7.7 # theme for mkdocs
|
||||
mkdocs-material-extensions==1.3.1 # extensions
|
||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -52,20 +56,27 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#endif
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS)
|
||||
|
||||
// Construct the namespace version component
|
||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
|
||||
@@ -471,6 +471,30 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = { std::get<Idx>(t)... };
|
||||
}
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. Build what the default deduction builds instead: an object
|
||||
// if the element is a [string, value] pair, a one-element array otherwise.
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||
{
|
||||
BasicJsonType element(std::get<0>(t));
|
||||
// same test as the initializer-list constructor, including the cast that
|
||||
// keeps a string type constructible from 0 from selecting operator[](key)
|
||||
const bool is_member = element.is_array() && element.size() == 2
|
||||
&& element[static_cast<typename BasicJsonType::size_type>(0)].is_string();
|
||||
if (is_member)
|
||||
{
|
||||
j = BasicJsonType::object({std::move(element)});
|
||||
}
|
||||
else
|
||||
{
|
||||
j = BasicJsonType::array({std::move(element)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
{
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <functional> // hash
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -26,6 +28,9 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
|
||||
return seed;
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j);
|
||||
|
||||
/*!
|
||||
@brief hash a JSON value
|
||||
|
||||
@@ -33,12 +38,21 @@ The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||
type of the JSON value is taken into account to have different hash values for
|
||||
null, 0, 0U, and false, etc.
|
||||
|
||||
Hashing an array or an object hashes its elements, which used to call this
|
||||
function again once per nesting level, so a value nested deeply enough
|
||||
exhausted the call stack and terminated the process. The descent is bounded
|
||||
here: once @ref recursion_depth_limit levels have been entered, @ref
|
||||
hash_iteratively hashes what is left without the call stack. A value nested
|
||||
less deeply than that - all but a vanishing minority - is hashed exactly as
|
||||
before, without allocating.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j JSON value to hash
|
||||
@param depth nesting level of @a j, counted from the value passed by the caller
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash(const BasicJsonType& j)
|
||||
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -56,22 +70,32 @@ std::size_t hash(const BasicJsonType& j)
|
||||
|
||||
case BasicJsonType::value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j.items())
|
||||
{
|
||||
const auto h = std::hash<string_t> {}(element.key());
|
||||
seed = combine(seed, h);
|
||||
seed = combine(seed, hash(element.value()));
|
||||
seed = combine(seed, hash(element.value(), depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j)
|
||||
{
|
||||
seed = combine(seed, hash(element));
|
||||
seed = combine(seed, hash(element, depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
@@ -127,5 +151,77 @@ std::size_t hash(const BasicJsonType& j)
|
||||
}
|
||||
}
|
||||
|
||||
/// an array or object whose elements @ref hash_iteratively is hashing
|
||||
template<typename BasicJsonType>
|
||||
struct hash_frame
|
||||
{
|
||||
hash_frame(const BasicJsonType* value_, std::size_t seed_) noexcept
|
||||
: value(value_), position(value_->cbegin()), seed(seed_)
|
||||
{}
|
||||
|
||||
const BasicJsonType* value;
|
||||
typename BasicJsonType::const_iterator position;
|
||||
std::size_t seed;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief hash the array or object @a j without the call stack
|
||||
|
||||
Computes the same value as @ref hash, keeping the arrays and objects it has
|
||||
entered on an explicit stack instead of descending into them. Only reached for
|
||||
values nested deeper than @ref recursion_depth_limit.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j array or object to hash
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
|
||||
std::vector<hash_frame<BasicJsonType>> stack;
|
||||
stack.emplace_back(&j, combine(static_cast<std::size_t>(j.type()), j.size()));
|
||||
|
||||
while (true)
|
||||
{
|
||||
// a copy, as entering an element below can reallocate the stack; the
|
||||
// frame itself is only changed through stack.back()
|
||||
const hash_frame<BasicJsonType> frame = stack.back();
|
||||
|
||||
if (frame.position == frame.value->cend())
|
||||
{
|
||||
// all elements are hashed: fold this value's hash into its parent's
|
||||
// seed, exactly where the recursive version returns it
|
||||
const std::size_t h = frame.seed;
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
return h;
|
||||
}
|
||||
stack.back().seed = combine(stack.back().seed, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.value->is_object())
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, std::hash<string_t> {}(frame.position.key()));
|
||||
}
|
||||
|
||||
// advance before entering the element, which pushes onto the stack
|
||||
const BasicJsonType& element = *frame.position;
|
||||
++stack.back().position;
|
||||
|
||||
if (element.is_structured())
|
||||
{
|
||||
stack.emplace_back(&element, combine(static_cast<std::size_t>(element.type()), element.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, hash(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include <nlohmann/detail/meta/is_sax.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -84,7 +83,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
|
||||
///////////////////
|
||||
|
||||
/*!
|
||||
@brief deserialization of CBOR, MessagePack, and UBJSON values
|
||||
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
|
||||
class binary_reader
|
||||
@@ -132,6 +131,7 @@ class binary_reader
|
||||
{
|
||||
sax = sax_;
|
||||
container_stack.clear();
|
||||
bon8_pushback_size = 0;
|
||||
bool result = false;
|
||||
|
||||
switch (format)
|
||||
@@ -153,6 +153,10 @@ class binary_reader
|
||||
result = parse_ubjson_internal();
|
||||
break;
|
||||
|
||||
case input_format_t::bon8:
|
||||
result = parse_bon8_internal();
|
||||
break;
|
||||
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
@@ -165,6 +169,11 @@ class binary_reader
|
||||
{
|
||||
get_ignore_noop();
|
||||
}
|
||||
else if (input_format == input_format_t::bon8)
|
||||
{
|
||||
// a string that ends a container hands back the byte after it
|
||||
get_bon8();
|
||||
}
|
||||
else
|
||||
{
|
||||
get();
|
||||
@@ -3172,6 +3181,511 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
//////////
|
||||
// BON8 //
|
||||
//////////
|
||||
|
||||
/*!
|
||||
@brief get the next byte of a BON8 value
|
||||
|
||||
A BON8 string has no length prefix and no mandatory terminator: it ends at
|
||||
the first byte that cannot continue it, which is already the first byte (or,
|
||||
for an integer that begins with a UTF-8 lead byte, the first two bytes) of
|
||||
whatever follows. The string reader hands those bytes back with
|
||||
@ref unget_bon8, and every BON8 read goes through this function so that
|
||||
they are seen again.
|
||||
|
||||
@return character read from the input
|
||||
*/
|
||||
char_int_type get_bon8()
|
||||
{
|
||||
if (bon8_pushback_size != 0)
|
||||
{
|
||||
++chars_read;
|
||||
return current = bon8_pushback[--bon8_pushback_size];
|
||||
}
|
||||
return get();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief hand a byte back so that the next @ref get_bon8 returns it again
|
||||
|
||||
@param[in] c the byte to hand back; bytes handed back are returned in
|
||||
reverse order
|
||||
*/
|
||||
void unget_bon8(const char_int_type c)
|
||||
{
|
||||
// At most two bytes are ever handed back: a byte is only handed back
|
||||
// right after it was read with get_bon8(), and the only place that
|
||||
// hands back two bytes (a lead byte and the byte after it) read both
|
||||
// of them in a row, which emptied the buffer first. This is an
|
||||
// invariant of the reader rather than a property of the input, so
|
||||
// an assertion suffices (the fuzzers are built with assertions).
|
||||
JSON_ASSERT(bon8_pushback_size < bon8_pushback.size());
|
||||
bon8_pushback[bon8_pushback_size++] = c;
|
||||
--chars_read;
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] c a byte
|
||||
@return whether @a c is a UTF-8 continuation byte (0x80..0xBF)
|
||||
*/
|
||||
static constexpr bool is_bon8_continuation(const char_int_type c) noexcept
|
||||
{
|
||||
return 0x80 <= c && c <= 0xBF;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a parse error at the last read byte
|
||||
|
||||
@param[in] detail a detailed error message
|
||||
@param[in] context further context information
|
||||
@return false
|
||||
*/
|
||||
bool bon8_error(const std::string& detail, const char* context)
|
||||
{
|
||||
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::bon8, concat(detail, ": 0x", last_token), context), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a BON8 value and everything nested inside it
|
||||
|
||||
Reads values until the one that was begun here is complete, resuming the
|
||||
enclosing container after each element, so that the nesting depth of the
|
||||
input costs heap rather than native stack (see #5104).
|
||||
|
||||
@return whether reading the value succeeded
|
||||
*/
|
||||
bool parse_bon8_internal()
|
||||
{
|
||||
// the key currently being read; hoisted out of the loop so that its
|
||||
// capacity is reused across elements and across nesting levels
|
||||
string_t key;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!container_stack.empty())
|
||||
{
|
||||
// a copy, not a reference: it must stay valid across the
|
||||
// pop_back() below, which destroys the container_stack element
|
||||
// it would otherwise alias
|
||||
const container_frame top = container_stack.back();
|
||||
bool at_end = false;
|
||||
|
||||
if (top.remaining != npos)
|
||||
{
|
||||
// counted container (0x80..0x84, 0x86..0x8A): it ends once
|
||||
// its elements have been read
|
||||
at_end = (top.remaining == 0);
|
||||
if (!at_end)
|
||||
{
|
||||
// claim the element about to be read
|
||||
--container_stack.back().remaining;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// container 0x85 or 0x8B: it ends at an end-of-container
|
||||
// marker (0xFE); any other byte begins the next element
|
||||
at_end = (get_bon8() == 0xFE);
|
||||
if (!at_end)
|
||||
{
|
||||
unget_bon8(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (at_end)
|
||||
{
|
||||
container_stack.pop_back();
|
||||
if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the value begun here is complete once its container is
|
||||
if (container_stack.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (top.is_object)
|
||||
{
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_bon8_value()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// a value that opened a container left it on the stack; one that
|
||||
// did not, and that was not inside a container, was the whole value
|
||||
if (container_stack.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read one BON8 value
|
||||
|
||||
Reads a single value and passes it to the SAX parser. A value that begins
|
||||
a container is not read to its end: the container is opened with
|
||||
@ref enter_container and its elements are read by
|
||||
@ref parse_bon8_internal, so that nesting does not consume native stack.
|
||||
|
||||
@return whether reading the value succeeded
|
||||
*/
|
||||
bool parse_bon8_value()
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "value");
|
||||
}
|
||||
|
||||
// string: ASCII character
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
string_t s;
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(s) && sax->string(s);
|
||||
}
|
||||
|
||||
// array with 0..4 elements
|
||||
if (byte <= 0x84)
|
||||
{
|
||||
return enter_array(static_cast<std::size_t>(byte - 0x80));
|
||||
}
|
||||
|
||||
// array terminated by 0xFE
|
||||
if (byte == 0x85)
|
||||
{
|
||||
return enter_array(npos);
|
||||
}
|
||||
|
||||
// object with 0..4 members
|
||||
if (byte <= 0x8A)
|
||||
{
|
||||
return enter_object(static_cast<std::size_t>(byte - 0x86));
|
||||
}
|
||||
|
||||
switch (byte)
|
||||
{
|
||||
case 0x8B: // object terminated by 0xFE
|
||||
return enter_object(npos);
|
||||
|
||||
case 0x8C: // int32
|
||||
{
|
||||
std::int32_t number{};
|
||||
return get_number(input_format_t::bon8, number) && emit_bon8_integer(number);
|
||||
}
|
||||
|
||||
case 0x8D: // int64
|
||||
{
|
||||
std::int64_t number{};
|
||||
return get_number(input_format_t::bon8, number) && emit_bon8_integer(number);
|
||||
}
|
||||
|
||||
case 0x8E: // binary32
|
||||
{
|
||||
float number{};
|
||||
return get_number(input_format_t::bon8, number) && sax->number_float(static_cast<number_float_t>(number), "");
|
||||
}
|
||||
|
||||
case 0x8F: // binary64
|
||||
{
|
||||
double number{};
|
||||
return get_number(input_format_t::bon8, number) && sax->number_float(static_cast<number_float_t>(number), "");
|
||||
}
|
||||
|
||||
case 0xF8:
|
||||
return sax->boolean(false);
|
||||
|
||||
case 0xF9:
|
||||
return sax->boolean(true);
|
||||
|
||||
case 0xFA:
|
||||
return sax->null();
|
||||
|
||||
case 0xFB:
|
||||
return sax->number_float(static_cast<number_float_t>(-1.0), "");
|
||||
|
||||
case 0xFC:
|
||||
return sax->number_float(static_cast<number_float_t>(0.0), "");
|
||||
|
||||
case 0xFD:
|
||||
return sax->number_float(static_cast<number_float_t>(1.0), "");
|
||||
|
||||
case 0xFF: // empty string
|
||||
{
|
||||
string_t s;
|
||||
return sax->string(s);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// integer 0..39
|
||||
if (byte <= 0xB7)
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(byte - 0x90));
|
||||
}
|
||||
|
||||
// integer -1..-10
|
||||
if (byte <= 0xC1)
|
||||
{
|
||||
return sax->number_integer(-1 - static_cast<number_integer_t>(byte - 0xB8));
|
||||
}
|
||||
|
||||
// 0xC2..0xF7: a UTF-8 lead byte begins a string if a continuation
|
||||
// byte follows and an integer otherwise
|
||||
if (byte <= 0xF7)
|
||||
{
|
||||
const auto second = get_bon8();
|
||||
if (is_bon8_continuation(second))
|
||||
{
|
||||
string_t s;
|
||||
unget_bon8(second);
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(s) && sax->string(s);
|
||||
}
|
||||
return get_bon8_integer(byte, second);
|
||||
}
|
||||
|
||||
// 0xFE: end of container where a value is expected
|
||||
return bon8_error("invalid byte", "value");
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass an integer to the SAX parser
|
||||
|
||||
Non-negative integers are passed as unsigned, negative integers as signed
|
||||
numbers, like the other binary formats do.
|
||||
|
||||
@param[in] number the integer
|
||||
@return whether the SAX parser accepted the value
|
||||
*/
|
||||
bool emit_bon8_integer(const std::int64_t number)
|
||||
{
|
||||
if (number >= 0)
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(number));
|
||||
}
|
||||
return sax->number_integer(static_cast<number_integer_t>(number));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read an integer encoded in 2..4 bytes
|
||||
|
||||
The first byte is a UTF-8 lead byte (0xC2..0xF7) that is followed by a
|
||||
byte that is not a continuation byte: 0x00..0x7F for positive and
|
||||
0xC0..0xFF for negative integers. The lead byte's low bits and the second
|
||||
byte's low 7 (positive) or 6 (negative) bits are the most significant bits
|
||||
of the value; 3- and 4-byte integers add one or two full bytes. Each range
|
||||
starts where the shorter one ends, so no value has two encodings of the
|
||||
same length.
|
||||
|
||||
@param[in] lead the first byte (0xC2..0xF7)
|
||||
@param[in] second the second byte
|
||||
@return whether reading the integer succeeded
|
||||
*/
|
||||
bool get_bon8_integer(const char_int_type lead, const char_int_type second)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bon8, "number")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool negative = second >= 0xC0;
|
||||
std::int64_t value = static_cast<std::int64_t>(negative ? (second & 0x3F) : second);
|
||||
std::int64_t offset = 0;
|
||||
int extra_bytes = 0;
|
||||
|
||||
if (lead <= 0xDF)
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead - 0xC2) << (negative ? 6 : 7);
|
||||
offset = negative ? 11 : 40;
|
||||
}
|
||||
else if (lead <= 0xEF)
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead & 0x0F) << (negative ? 6 : 7);
|
||||
offset = negative ? 1931 : 3880;
|
||||
extra_bytes = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead & 0x07) << (negative ? 6 : 7);
|
||||
offset = negative ? 264075 : 528168;
|
||||
extra_bytes = 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < extra_bytes; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(get_bon8() == char_traits<char_type>::eof()))
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "number");
|
||||
}
|
||||
value = (value << 8) | static_cast<std::int64_t>(current);
|
||||
}
|
||||
|
||||
return negative ? sax->number_integer(static_cast<number_integer_t>(-(value + offset)))
|
||||
: sax->number_unsigned(static_cast<number_unsigned_t>(value + offset));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read an object key
|
||||
|
||||
A key must be a string, so its first byte must be an ASCII character, a
|
||||
UTF-8 lead byte followed by a continuation byte, or 0xFF (empty string).
|
||||
|
||||
@param[out] result the key
|
||||
@return whether reading the key succeeded
|
||||
*/
|
||||
bool get_bon8_key(string_t& result)
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "key");
|
||||
}
|
||||
|
||||
if (byte == 0xFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(result);
|
||||
}
|
||||
|
||||
if (0xC2 <= byte && byte <= 0xF7)
|
||||
{
|
||||
const auto second = get_bon8();
|
||||
unget_bon8(second);
|
||||
if (is_bon8_continuation(second))
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(result);
|
||||
}
|
||||
// an integer: report its first byte rather than the one after it
|
||||
current = byte;
|
||||
}
|
||||
|
||||
return bon8_error("expected a string; last byte", "key");
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a string
|
||||
|
||||
Reads UTF-8 characters until an end-of-string marker (0xFF), which is
|
||||
consumed, or a byte that cannot continue the string, which is handed back
|
||||
to be read as the start of the next value. The string must be valid UTF-8,
|
||||
and it must not end at the end of the input: the last string of a message
|
||||
is always terminated by 0xFF.
|
||||
|
||||
@param[out] result the string
|
||||
@return whether reading the string succeeded
|
||||
*/
|
||||
bool get_bon8_string(string_t& result)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "string");
|
||||
}
|
||||
|
||||
// end of string
|
||||
if (byte == 0xFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// ASCII character
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
result.push_back(static_cast<typename string_t::value_type>(byte));
|
||||
continue;
|
||||
}
|
||||
|
||||
// a byte that cannot begin a character ends the string and begins
|
||||
// the next value
|
||||
if (byte < 0xC2 || byte > 0xF7)
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return true;
|
||||
}
|
||||
|
||||
// a lead byte ends the string if no continuation byte follows: it
|
||||
// is then the first byte of an integer
|
||||
const auto second = get_bon8();
|
||||
if (!is_bon8_continuation(second))
|
||||
{
|
||||
unget_bon8(second);
|
||||
unget_bon8(byte);
|
||||
return true;
|
||||
}
|
||||
|
||||
// the valid range of the second byte excludes overlong forms,
|
||||
// surrogates, and code points above U+10FFFF
|
||||
// (RFC 3629, section 4)
|
||||
int continuation_bytes = 0;
|
||||
bool valid_second = true;
|
||||
if (byte <= 0xDF)
|
||||
{
|
||||
continuation_bytes = 1;
|
||||
}
|
||||
else if (byte <= 0xEF)
|
||||
{
|
||||
continuation_bytes = 2;
|
||||
valid_second = (byte != 0xE0 || second >= 0xA0) && (byte != 0xED || second <= 0x9F);
|
||||
}
|
||||
else
|
||||
{
|
||||
continuation_bytes = 3;
|
||||
valid_second = byte <= 0xF4 && (byte != 0xF0 || second >= 0x90) && (byte != 0xF4 || second <= 0x8F);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!valid_second))
|
||||
{
|
||||
return bon8_error("invalid UTF-8 byte", "string");
|
||||
}
|
||||
|
||||
result.push_back(static_cast<typename string_t::value_type>(byte));
|
||||
result.push_back(static_cast<typename string_t::value_type>(second));
|
||||
|
||||
for (int i = 1; i < continuation_bytes; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(get_bon8() == char_traits<char_type>::eof()))
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "string");
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current)))
|
||||
{
|
||||
return bon8_error("invalid UTF-8 byte", "string");
|
||||
}
|
||||
result.push_back(static_cast<typename string_t::value_type>(current));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// Utility functions //
|
||||
///////////////////////
|
||||
@@ -3305,24 +3819,7 @@ class binary_reader
|
||||
const NumberType len,
|
||||
string_t& result)
|
||||
{
|
||||
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)))
|
||||
{
|
||||
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;
|
||||
return get_bytes(format, len, "string", result);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -3466,6 +3963,10 @@ class binary_reader
|
||||
error_msg += "BJData";
|
||||
break;
|
||||
|
||||
case input_format_t::bon8:
|
||||
error_msg += "BON8";
|
||||
break;
|
||||
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
@@ -3498,6 +3999,11 @@ class binary_reader
|
||||
/// the containers that have been opened and not closed yet; see @ref container_frame
|
||||
std::vector<container_frame> container_stack{};
|
||||
|
||||
/// BON8: bytes read past the end of a string, returned again by @ref get_bon8
|
||||
std::array<char_int_type, 2> bon8_pushback{{}};
|
||||
/// BON8: number of bytes in @ref bon8_pushback
|
||||
std::size_t bon8_pushback_size = 0;
|
||||
|
||||
// excluded markers in bjdata optimized type
|
||||
#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \
|
||||
make_array<char_int_type>('F', 'H', 'N', 'S', 'T', 'Z', '[', '{')
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace detail
|
||||
{
|
||||
|
||||
/// the supported input formats
|
||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };
|
||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||
|
||||
////////////////////
|
||||
// input adapters //
|
||||
@@ -762,6 +762,21 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
|
||||
template<typename T, std::size_t N>
|
||||
auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
// A `char` array from string-literal initialization (e.g. json::parse("123"))
|
||||
// carries a trailing '\0' contributed by the compiler, not by the source
|
||||
// text; drop exactly that one byte so it is not mistaken for real trailing
|
||||
// data. Every other element type (unsigned char, std::uint8_t, ...) keeps
|
||||
// the full extent unconditionally, since a trailing zero byte there is
|
||||
// genuine data (e.g. CBOR/MessagePack). This intentionally does not
|
||||
// strlen()-scan the array (as the pointer overload above does for a
|
||||
// null-delimited string): for a `char` array that is not NUL-terminated
|
||||
// within its bounds, that would read past the end of the array.
|
||||
if (std::is_same<typename std::remove_cv<T>::type, char>::value && N > 0 && array[N - 1] == 0)
|
||||
{
|
||||
return input_adapter(array, array + N - 1);
|
||||
}
|
||||
#endif
|
||||
return input_adapter(array, array + N);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <algorithm> // find_if, min
|
||||
#include <cstddef>
|
||||
#include <string> // string
|
||||
#include <type_traits> // enable_if_t
|
||||
#include <utility> // move
|
||||
#include <utility> // move, pair
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
@@ -278,7 +278,7 @@ class json_sax_dom_parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -327,7 +327,7 @@ class json_sax_dom_parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
@@ -611,7 +611,7 @@ class json_sax_dom_callback_parser
|
||||
// check object limit
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -631,7 +631,17 @@ class json_sax_dom_callback_parser
|
||||
// add discarded value at the given key and store the reference for later
|
||||
if (keep && ref_stack.back())
|
||||
{
|
||||
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
const auto it = obj.find(val);
|
||||
if (it != obj.end())
|
||||
{
|
||||
// this is a duplicate key (legal in JSON); remember its
|
||||
// current value so it can be restored later if the new
|
||||
// value is rejected by the callback, instead of being
|
||||
// erased together with the discarded placeholder
|
||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||
}
|
||||
object_element = &(obj[val] = discarded);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -643,13 +653,18 @@ class json_sax_dom_callback_parser
|
||||
{
|
||||
if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
|
||||
{
|
||||
// discard object
|
||||
*ref_stack.back() = discarded;
|
||||
// discard object, unless this slot holds a duplicate key's
|
||||
// previous value pending restoration, in which case that
|
||||
// value is restored instead of being discarded
|
||||
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||
{
|
||||
*ref_stack.back() = discarded;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded object.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
// Set start/end positions for discarded object.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -663,6 +678,10 @@ class json_sax_dom_callback_parser
|
||||
#endif
|
||||
|
||||
ref_stack.back()->set_parents();
|
||||
// this object is finally, definitively kept; drop any
|
||||
// pending duplicate-key stash entry for its slot since it
|
||||
// can no longer be restored
|
||||
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +730,7 @@ class json_sax_dom_callback_parser
|
||||
// check array limit
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
@@ -743,16 +762,25 @@ class json_sax_dom_callback_parser
|
||||
#endif
|
||||
|
||||
ref_stack.back()->set_parents();
|
||||
// this array is finally, definitively kept; drop any
|
||||
// pending duplicate-key stash entry for its slot since it
|
||||
// can no longer be restored
|
||||
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// discard array
|
||||
*ref_stack.back() = discarded;
|
||||
// discard array, unless this slot holds a duplicate key's
|
||||
// previous value pending restoration, in which case that
|
||||
// value is restored instead of being discarded
|
||||
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||
{
|
||||
*ref_stack.back() = discarded;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded array.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
// Set start/end positions for discarded array.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +897,35 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
#endif
|
||||
|
||||
/// if there is a pending duplicate-key stash entry for this exact slot,
|
||||
/// remove it from the stash; if restore_value is true, the stashed
|
||||
/// previous value is moved back into the slot first (use this when the
|
||||
/// new value at that slot was rejected); otherwise the stash entry is
|
||||
/// simply dropped (use this when the new value was accepted, so it
|
||||
/// correctly supersedes the old one and no restore should ever happen
|
||||
/// for this slot again)
|
||||
/// @return whether a matching stash entry was found (and processed)
|
||||
bool resolve_duplicate_key_stash(BasicJsonType* slot, bool restore_value)
|
||||
{
|
||||
const auto it = std::find_if(duplicate_key_stash.begin(), duplicate_key_stash.end(),
|
||||
[slot](const std::pair<BasicJsonType*, BasicJsonType>& entry)
|
||||
{
|
||||
return entry.first == slot;
|
||||
});
|
||||
|
||||
if (it == duplicate_key_stash.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (restore_value)
|
||||
{
|
||||
*slot = std::move(it->second);
|
||||
}
|
||||
duplicate_key_stash.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the key the value now being handled will be stored under
|
||||
|
||||
@@ -887,7 +944,9 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief remove the discarded value the callback rejected from its parent
|
||||
@brief remove the discarded value the callback rejected from its parent,
|
||||
unless it is a duplicate key's slot with a stashed previous value, in
|
||||
which case that previous value is restored instead
|
||||
|
||||
A rejected value can only ever be the one most recently added to @a parent:
|
||||
the last element of an array, or the placeholder key() stored under @a key
|
||||
@@ -902,7 +961,7 @@ class json_sax_dom_callback_parser
|
||||
@param[in,out] parent the container to remove the rejected value from
|
||||
@param[in] key the key the value was stored under; unused for arrays
|
||||
*/
|
||||
static void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||
void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||
{
|
||||
if (parent.is_array())
|
||||
{
|
||||
@@ -918,7 +977,12 @@ class json_sax_dom_callback_parser
|
||||
const auto it = object.find(key);
|
||||
if (it != object.end() && it->second.is_discarded())
|
||||
{
|
||||
object.erase(it);
|
||||
// a duplicate key's slot has a stashed previous value that
|
||||
// must be restored instead of being erased
|
||||
if (!resolve_duplicate_key_stash(&it->second, true))
|
||||
{
|
||||
object.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1020,6 +1084,16 @@ class json_sax_dom_callback_parser
|
||||
|
||||
JSON_ASSERT(object_element);
|
||||
*object_element = std::move(value);
|
||||
if (!skip_callback)
|
||||
{
|
||||
// this scalar value finally, definitively replaces whatever was
|
||||
// at this slot; drop any pending duplicate-key stash entry for
|
||||
// it since it can no longer be restored (a container value at
|
||||
// this slot is resolved later, in end_object()/end_array(),
|
||||
// since skip_callback is true for the placeholder handling that
|
||||
// happens here for those)
|
||||
resolve_duplicate_key_stash(object_element, false);
|
||||
}
|
||||
return {true, object_element};
|
||||
}
|
||||
|
||||
@@ -1039,6 +1113,12 @@ class json_sax_dom_callback_parser
|
||||
std::vector<string_t> container_key_stack {}; // NOLINT(readability-redundant-member-init)
|
||||
/// helper to hold the reference for the next object element
|
||||
BasicJsonType* object_element = nullptr;
|
||||
/// stash of (slot pointer, previous value) for object members that
|
||||
/// already existed when key() was called again for the same key
|
||||
/// (duplicate keys); used to restore the previous value if the new
|
||||
/// value is later rejected by the callback, instead of erasing the
|
||||
/// member entirely
|
||||
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
|
||||
/// whether a syntax error occurred
|
||||
bool errored = false;
|
||||
/// callback function
|
||||
|
||||
@@ -952,7 +952,9 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
case '\n':
|
||||
case '\r':
|
||||
case char_traits<char_type>::eof():
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -970,8 +972,10 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
switch (get())
|
||||
{
|
||||
case char_traits<char_type>::eof():
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
case char_traits<char_type>::eof():
|
||||
{
|
||||
error_message = "invalid comment; missing closing '*/'";
|
||||
return false;
|
||||
@@ -2153,9 +2157,12 @@ scan_number_done:
|
||||
case '9':
|
||||
return scan_number_dispatch(std::integral_constant<bool, bulk_scan> {});
|
||||
|
||||
// end of input (the null byte is needed when parsing from
|
||||
// string literals)
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
// end of input; by default, a null byte is also treated as end of
|
||||
// input for backwards compatibility (see JSON_STRICT_NUL_HANDLING
|
||||
// to opt into rejecting a null byte in the input instead)
|
||||
case char_traits<char_type>::eof():
|
||||
return token_type::end_of_input;
|
||||
|
||||
|
||||
@@ -186,6 +186,15 @@
|
||||
#define JSON_NO_UNIQUE_ADDRESS
|
||||
#endif
|
||||
|
||||
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||
// constructor uses to bound its descent: every test that copies a value
|
||||
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||
// GCC targeting MinGW and with every other toolchain the library is tested on.
|
||||
// Copying works the same way without the counter, only more slowly.
|
||||
#if !defined(JSON_NO_THREAD_LOCAL) && defined(__clang__) && defined(__MINGW32__)
|
||||
#define JSON_NO_THREAD_LOCAL 1
|
||||
#endif
|
||||
|
||||
// disable documentation warnings on clang
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
@@ -804,6 +813,6 @@ void templated_json_throw(ExceptionType exception)
|
||||
#define JSON_USE_GLOBAL_UDLS 1
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#ifndef JSON_STRICT_NUL_HANDLING
|
||||
#define JSON_STRICT_NUL_HANDLING 0
|
||||
#endif
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_USE_GLOBAL_UDLS
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#undef JSON_STRICT_NUL_HANDLING
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
@@ -44,6 +44,7 @@
|
||||
#undef JSON_HAS_STD_FORMAT
|
||||
#undef JSON_HAS_STATIC_RTTI
|
||||
#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
#include <iterator> // back_inserter
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
@@ -44,22 +45,32 @@ template<typename CharType> struct output_adapter_protocol
|
||||
template<typename CharType>
|
||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
|
||||
/// output adapter for byte vectors
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
/// passed to binary_writer by value as a template parameter, so
|
||||
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
||||
/// vtable lookup and no shared_ptr. It is used for the common
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
||||
/// wraps this same sink to provide the virtual interface.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
void write_character(CharType c)
|
||||
{
|
||||
v.push_back(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values. Appending an empty
|
||||
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
||||
// virtual base, and the concrete sink must do the same.
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
v.insert(v.end(), s, s + length);
|
||||
}
|
||||
@@ -68,6 +79,34 @@ class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
std::vector<CharType, AllocatorType>& v;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
///
|
||||
/// The appending itself lives in output_vector_sink; this class only adds the
|
||||
/// virtual output_adapter_protocol interface on top of it, so both the
|
||||
/// type-erased and the templated path share one implementation.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: sink(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
sink.write_character(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
sink.write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_vector_sink<CharType, AllocatorType> sink;
|
||||
};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// output adapter for output streams
|
||||
template<typename CharType>
|
||||
@@ -118,6 +157,39 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
StringType& str;
|
||||
};
|
||||
|
||||
/// @brief output sink forwarding to a type-erased output adapter
|
||||
///
|
||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
||||
/// avoid it.
|
||||
template<typename CharType>
|
||||
class output_adapter_sink
|
||||
{
|
||||
public:
|
||||
explicit output_adapter_sink(output_adapter_t<CharType> adapter)
|
||||
: oa(std::move(adapter))
|
||||
{
|
||||
JSON_ASSERT(oa);
|
||||
}
|
||||
|
||||
void write_character(CharType c)
|
||||
{
|
||||
oa->write_character(c);
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||
// the type-erased path already did before this sink existed
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_adapter_t<CharType> oa;
|
||||
};
|
||||
|
||||
template<typename CharType, typename StringType = std::basic_string<CharType>>
|
||||
class output_adapter
|
||||
{
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -58,6 +58,8 @@ class serializer
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
||||
static constexpr std::uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr std::uint8_t UTF8_REJECT = 1;
|
||||
|
||||
public:
|
||||
/*!
|
||||
@@ -132,7 +134,7 @@ class serializer
|
||||
|
||||
Serializing a container descends into its elements, so a value nested deeply
|
||||
enough used to exhaust the call stack and terminate the process with no
|
||||
exception to catch. The descent is bounded here: once @ref dump_depth_limit
|
||||
exception to catch. The descent is bounded here: once @ref recursion_depth_limit
|
||||
levels have been entered, @ref dump_iteratively writes out what is left
|
||||
without the call stack. A value nested less deeply than that - all but a
|
||||
vanishing minority - is written by exactly the code that always wrote it.
|
||||
@@ -147,7 +149,7 @@ class serializer
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -222,7 +224,7 @@ class serializer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -407,19 +409,12 @@ class serializer
|
||||
}
|
||||
|
||||
private:
|
||||
/// the number of levels @ref dump_internal descends into before it hands
|
||||
/// over to @ref dump_iteratively
|
||||
static constexpr std::size_t dump_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a val and everything below it without the call stack
|
||||
|
||||
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
||||
entered on an explicit stack instead of descending into them. Only reached
|
||||
for values nested deeper than @ref dump_depth_limit, which is why it is not
|
||||
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
||||
written for speed: walking every value this way measured up to 20% slower on
|
||||
object-heavy documents than letting the compiler drive the descent.
|
||||
*/
|
||||
@@ -1597,6 +1592,62 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string is UTF-8 encoded
|
||||
|
||||
The function checks each byte of a string whether it is UTF-8 encoded. The
|
||||
result of the check is stored in the @a state parameter. The function must
|
||||
be called initially with state 0 (accept). State 1 means the string must
|
||||
be rejected, because the current byte is not allowed. If the string is
|
||||
completely processed, but the state is non-zero, the string ended
|
||||
prematurely; that is, the last byte indicated more bytes should have
|
||||
followed.
|
||||
|
||||
@param[in,out] state the state of the decoding
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note The function has been edited: a std::array is used.
|
||||
|
||||
@copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
||||
const std::uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6u)
|
||||
: (0xFFu >> type) & (byte);
|
||||
|
||||
const std::size_t index = 256u + (static_cast<size_t>(state) * 16u) + static_cast<size_t>(type);
|
||||
JSON_ASSERT(index < utf8d.size());
|
||||
state = utf8d[index];
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload to make the compiler happy while it is instantiating
|
||||
* dump_integer for number_unsigned_t.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief the number of nesting levels an operation recurses into
|
||||
|
||||
Operations that walk a value (serializing, hashing, merging, ...) recurse once
|
||||
per nesting level, which is fastest, but a value nested deeply enough would
|
||||
exhaust the call stack. So they recurse only this many levels deep and finish
|
||||
whatever lies below with an explicit stack. All of them share this limit.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
constexpr std::size_t recursion_depth_limit() noexcept
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -8,13 +8,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <string> // string, to_string
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -36,99 +33,5 @@ StringType to_string(std::size_t value)
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// UTF-8 decoding //
|
||||
///////////////////
|
||||
|
||||
// UTF-8 decoder states used by decode() below
|
||||
static constexpr std::uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr std::uint8_t UTF8_REJECT = 1;
|
||||
|
||||
/*!
|
||||
@brief process a byte of a UTF-8 sequence
|
||||
|
||||
This is a single-byte step of a "shift-based" UTF-8 decoder originally
|
||||
written by Björn Hoehrmann. See
|
||||
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
|
||||
This decoder is the single source of truth for UTF-8 validation in this
|
||||
library: it is used both by the serializer (to escape and, in strict mode,
|
||||
reject ill-formed UTF-8 when dumping a string) and by the binary readers
|
||||
(to reject ill-formed UTF-8 in CBOR/MessagePack/BSON/UBJSON text strings at
|
||||
decode time; see @ref is_valid_utf8 below).
|
||||
|
||||
@param[in,out] state the current decoder state
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note Original source: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
||||
const std::uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6u)
|
||||
: (0xFFu >> type) & (byte);
|
||||
|
||||
const std::size_t index = 256u + (static_cast<std::size_t>(state) * 16u) + static_cast<std::size_t>(type);
|
||||
JSON_ASSERT(index < utf8d.size());
|
||||
state = utf8d[index];
|
||||
return state;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string consists solely of valid UTF-8
|
||||
|
||||
Used by the CBOR/MessagePack/BSON/UBJSON binary readers to reject text
|
||||
strings that are not valid UTF-8 at decode time (RFC 8949 §3.1 and the
|
||||
MessagePack/BSON specifications all require text strings to be UTF-8), so
|
||||
that malformed input is caught immediately instead of only surfacing later
|
||||
as a type_error.316 when the resulting value is dumped.
|
||||
|
||||
@param[in] s the string to check
|
||||
@return whether @a s is valid UTF-8
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline bool is_valid_utf8(const StringType& s) noexcept
|
||||
{
|
||||
std::uint8_t state = UTF8_ACCEPT;
|
||||
std::uint32_t codepoint = 0;
|
||||
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
decode(state, codepoint, static_cast<std::uint8_t>(s[i]));
|
||||
if (state == UTF8_REJECT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return state == UTF8_ACCEPT;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
+737
-78
@@ -28,14 +28,14 @@
|
||||
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
||||
#endif
|
||||
|
||||
#include <algorithm> // all_of, find, for_each
|
||||
#include <algorithm> // all_of, find, for_each, none_of
|
||||
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||
#include <functional> // hash, less
|
||||
#include <initializer_list> // initializer_list
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // istream, ostream
|
||||
#endif // JSON_NO_IO
|
||||
#include <iterator> // random_access_iterator_tag
|
||||
#include <iterator> // make_move_iterator, random_access_iterator_tag
|
||||
#include <memory> // unique_ptr
|
||||
#include <string> // string, stoi, to_string
|
||||
#include <utility> // declval, forward, move, pair, swap
|
||||
@@ -68,6 +68,7 @@
|
||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/output/serializer.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <nlohmann/ordered_map.hpp>
|
||||
@@ -140,7 +141,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
friend ::nlohmann::detail::serializer<basic_json>;
|
||||
template<typename BasicJsonType>
|
||||
friend class ::nlohmann::detail::iter_impl;
|
||||
template<typename BasicJsonType, typename CharType>
|
||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
||||
friend class ::nlohmann::detail::binary_writer;
|
||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||
friend class ::nlohmann::detail::binary_reader;
|
||||
@@ -188,6 +189,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
template<typename InputType>
|
||||
using binary_reader = ::nlohmann::detail::binary_reader<basic_json, InputType>;
|
||||
template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>;
|
||||
// binary_writer over a concrete (non-virtual) sink appending into a std::vector,
|
||||
// used by the vector-returning to_* overloads
|
||||
template<typename CharType> using vector_binary_writer =
|
||||
::nlohmann::detail::binary_writer<basic_json, CharType, ::nlohmann::detail::output_vector_sink<CharType>>;
|
||||
template<typename CharType> static vector_binary_writer<CharType> vector_writer(std::vector<CharType>& v)
|
||||
{
|
||||
return vector_binary_writer<CharType>(::nlohmann::detail::output_vector_sink<CharType>(v));
|
||||
}
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
using serializer = ::nlohmann::detail::serializer<basic_json>;
|
||||
@@ -888,6 +897,352 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return j;
|
||||
}
|
||||
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
/// the number of levels an operation descends into before it finishes the
|
||||
/// value below it without the call stack
|
||||
static constexpr std::uint8_t nesting_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief how many levels the operation going on in this thread has descended into
|
||||
|
||||
Copying a value and comparing two values share this count. The library never
|
||||
nests one inside the other - copying a value does not compare one, and
|
||||
comparing two values does not copy them - and where user code nests them
|
||||
anyway, sharing the count only ends a descent sooner than it had to, which
|
||||
costs a little speed and is never wrong.
|
||||
|
||||
A byte is enough: the count never exceeds the limit by more than the single
|
||||
level that notices the limit has been reached.
|
||||
*/
|
||||
static std::uint8_t& nesting_depth() noexcept
|
||||
{
|
||||
static thread_local std::uint8_t depth = 0; // NOLINT(misc-use-internal-linkage)
|
||||
return depth;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@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
|
||||
|
||||
Looks the count up and tests it against the limit itself, rather than
|
||||
leaving that to the caller: either way it is reached exactly once, so
|
||||
there is nothing to be gained by making the caller do it.
|
||||
|
||||
Does nothing and is never @ref okay without thread-local storage, where no
|
||||
descent can be bounded at all: a caller that only descends while this says
|
||||
it may always ends up finishing without the call stack, exactly as if every
|
||||
value were nested past the limit.
|
||||
*/
|
||||
class nesting_depth_guard
|
||||
{
|
||||
public:
|
||||
nesting_depth_guard() noexcept
|
||||
#ifdef JSON_NO_THREAD_LOCAL
|
||||
: m_okay(false)
|
||||
#else
|
||||
: m_okay(nesting_depth() < nesting_depth_limit())
|
||||
#endif
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
++nesting_depth();
|
||||
#endif
|
||||
}
|
||||
|
||||
~nesting_depth_guard()
|
||||
{
|
||||
#ifndef JSON_NO_THREAD_LOCAL
|
||||
--nesting_depth();
|
||||
#endif
|
||||
}
|
||||
|
||||
nesting_depth_guard(const nesting_depth_guard&) = delete;
|
||||
nesting_depth_guard& operator=(const nesting_depth_guard&) = delete;
|
||||
nesting_depth_guard(nesting_depth_guard&&) = delete;
|
||||
nesting_depth_guard& operator=(nesting_depth_guard&&) = delete;
|
||||
|
||||
bool okay() const noexcept
|
||||
{
|
||||
return m_okay;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_okay;
|
||||
};
|
||||
|
||||
/// an entry of the iterative deep copy's worklist: a structured value and
|
||||
/// the value that is to become its copy
|
||||
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
|
||||
using copy_scratch_t = std::vector<std::pair<typename object_t::key_type, basic_json>>;
|
||||
|
||||
/// @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)
|
||||
{
|
||||
// a custom base class is only required to be copy-constructible and
|
||||
// move-assignable, so the copy has to go through a temporary
|
||||
static_cast<json_base_class_t&>(dst) = json_base_class_t(static_cast<const json_base_class_t&>(src));
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
dst.start_position = src.start_position;
|
||||
dst.end_position = src.end_position;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief copy the value of @a src into @a dst, which must not be structured
|
||||
|
||||
Objects and arrays are left alone: creating those is the one thing the copy
|
||||
constructor and @ref copy_shallow do differently from one another, and it is
|
||||
the reason copying a value can descend at all.
|
||||
*/
|
||||
/// @note inlined on purpose: both callers have already told an object or an
|
||||
/// array apart from the rest, and letting the compiler fold that test
|
||||
/// into this switch is worth a few percent when copying a value made
|
||||
/// mostly of numbers
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void copy_leaf_value(const basic_json& src, basic_json& dst)
|
||||
{
|
||||
switch (src.m_data.m_type)
|
||||
{
|
||||
case value_t::string:
|
||||
{
|
||||
dst.m_data.m_value = *src.m_data.m_value.string;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
dst.m_data.m_value = *src.m_data.m_value.binary;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::boolean:
|
||||
{
|
||||
dst.m_data.m_value = src.m_data.m_value.boolean;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
dst.m_data.m_value = src.m_data.m_value.number_integer;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
dst.m_data.m_value = src.m_data.m_value.number_unsigned;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
dst.m_data.m_value = src.m_data.m_value.number_float;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::null:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief copy everything of @a src into the null value @a dst but the children
|
||||
|
||||
Objects and arrays are not copied here; they are appended to @a worklist to
|
||||
be created later by @ref copy_iteratively. Until that happens, @a dst remains
|
||||
a null value, so that a partially built copy can be destroyed at any point
|
||||
without ever violating the class invariants.
|
||||
*/
|
||||
static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
|
||||
{
|
||||
copy_metadata(src, dst);
|
||||
|
||||
if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array)
|
||||
{
|
||||
// defer: dst stays a null value until its container exists
|
||||
worklist.emplace_back(&src, &dst);
|
||||
return;
|
||||
}
|
||||
|
||||
copy_leaf_value(src, dst);
|
||||
|
||||
// only now that the value exists may the type be set: had the creation
|
||||
// of the value thrown, dst would have been left as a valid null value
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
}
|
||||
|
||||
/// @brief create the copy of the array @a src in @a dst
|
||||
/// @note structured elements are appended to @a worklist instead
|
||||
static void copy_array_level(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
|
||||
{
|
||||
const array_t& src_array = *src.m_data.m_value.array;
|
||||
|
||||
// create all elements up front: growing the array afterwards could
|
||||
// invalidate the pointers that are handed to the worklist; resize()
|
||||
// rather than the fill constructor, because not every array type
|
||||
// provides the latter (e.g., ones without a matching allocator-aware
|
||||
// fill constructor)
|
||||
dst.m_data.m_value.array = create<array_t>();
|
||||
dst.m_data.m_value.array->resize(src_array.size());
|
||||
|
||||
auto dst_it = dst.m_data.m_value.array->begin();
|
||||
for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it)
|
||||
{
|
||||
copy_shallow(*src_it, *dst_it, worklist);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief create the copy of the object @a src in @a dst
|
||||
/// @note structured values are appended to @a worklist instead
|
||||
static void copy_object_level(const basic_json& src, basic_json& dst,
|
||||
copy_worklist_t& worklist, copy_scratch_t& scratch)
|
||||
{
|
||||
const object_t& src_object = *src.m_data.m_value.object;
|
||||
|
||||
// build the complete key skeleton and hand it to the object's range
|
||||
// constructor: adding the keys one by one would be quadratic for object
|
||||
// types that are backed by a vector, such as nlohmann::ordered_map
|
||||
scratch.clear();
|
||||
scratch.reserve(src_object.size());
|
||||
for (const auto& element : src_object)
|
||||
{
|
||||
scratch.emplace_back(element.first, basic_json());
|
||||
}
|
||||
|
||||
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
|
||||
std::make_move_iterator(scratch.end()));
|
||||
scratch.clear();
|
||||
|
||||
// pair every value of the copy with its counterpart in the original;
|
||||
// both are enumerated in the same order for every object type with a
|
||||
// deterministic order, so the lookup is only needed for exotic ones
|
||||
auto src_it = src_object.cbegin();
|
||||
for (auto& element : *dst.m_data.m_value.object)
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && src_it->first == element.first))
|
||||
{
|
||||
copy_shallow(src_it->second, element.second, worklist);
|
||||
++src_it;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto found = src_object.find(element.first);
|
||||
JSON_ASSERT(found != src_object.cend());
|
||||
copy_shallow(found->second, element.second, worklist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief deep-copy the object or array @a src into this value without recursing
|
||||
|
||||
The values whose copy has not been created yet are kept on an explicit
|
||||
worklist rather than on the call stack. This is only reached for values
|
||||
nested deeper than @ref nesting_depth_limit levels, which is why it copies
|
||||
every container by hand instead of letting the container do it: the fast
|
||||
ways of doing so would descend into the elements and defeat the purpose.
|
||||
*/
|
||||
void copy_iteratively(const basic_json& src)
|
||||
{
|
||||
copy_worklist_t worklist;
|
||||
copy_scratch_t scratch;
|
||||
|
||||
const basic_json* src_value = &src;
|
||||
basic_json* dst_value = this;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (src_value->m_data.m_type == value_t::array)
|
||||
{
|
||||
copy_array_level(*src_value, *dst_value, worklist);
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_object_level(*src_value, *dst_value, worklist, scratch);
|
||||
}
|
||||
|
||||
// the container is complete and will not be modified again
|
||||
dst_value->set_parents();
|
||||
|
||||
if (worklist.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& next = worklist.back();
|
||||
src_value = next.first;
|
||||
dst_value = next.second;
|
||||
worklist.pop_back();
|
||||
|
||||
// the value stops being a null value exactly here
|
||||
dst_value->m_data.m_type = src_value->m_data.m_type;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief copy one level of the object or array @a src into this value
|
||||
|
||||
The container copies its own elements, which is the fastest way to fill it.
|
||||
Every element that is structured itself comes back to @ref copy_structured.
|
||||
*/
|
||||
void copy_level(const basic_json& src)
|
||||
{
|
||||
if (m_data.m_type == value_t::object)
|
||||
{
|
||||
m_data.m_value = *src.m_data.m_value.object;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data.m_value = *src.m_data.m_value.array;
|
||||
}
|
||||
|
||||
set_parents();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief deep-copy the object or array @a src into this value
|
||||
|
||||
Copying a container copies its elements, so a value nested deeply enough
|
||||
used to exhaust the call stack. The descent is bounded here: the first
|
||||
@ref nesting_depth_limit levels are copied by the containers themselves, just
|
||||
as they always were, and anything below that is copied without the call
|
||||
stack by @ref copy_iteratively. Copying a value can therefore no longer
|
||||
exhaust the stack, however deeply it is nested, just like destroying one
|
||||
cannot since #1436.
|
||||
|
||||
Nothing has to be scanned or built by hand to reach that: a value that is
|
||||
not nested deeper than the limit - all but a vanishing minority - is copied
|
||||
exactly as it was before, and this whole detour costs it one counter.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
void copy_structured(const basic_json& src)
|
||||
{
|
||||
const nesting_depth_guard guard;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(guard.okay()))
|
||||
{
|
||||
copy_level(src);
|
||||
return;
|
||||
}
|
||||
|
||||
// Finish this value without descending any further. It is completed
|
||||
// before this returns, so a copy made by a custom base class - or by
|
||||
// anything else that runs while a copy is going on - is unaffected by
|
||||
// the copy it is nested in.
|
||||
copy_iteratively(src);
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
//////////////////////////
|
||||
// JSON parser callback //
|
||||
@@ -1267,60 +1622,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// check of passed value is valid
|
||||
other.assert_invariant();
|
||||
|
||||
switch (m_data.m_type)
|
||||
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
m_data.m_value = *other.m_data.m_value.object;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
m_data.m_value = *other.m_data.m_value.array;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
m_data.m_value = *other.m_data.m_value.string;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::boolean:
|
||||
{
|
||||
m_data.m_value = other.m_data.m_value.boolean;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
m_data.m_value = other.m_data.m_value.number_integer;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
m_data.m_value = other.m_data.m_value.number_unsigned;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
m_data.m_value = other.m_data.m_value.number_float;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
m_data.m_value = *other.m_data.m_value.binary;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
// copying the container directly would call this constructor again
|
||||
// for every element, once per nesting level
|
||||
copy_structured(other);
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_leaf_value(other, *this);
|
||||
}
|
||||
|
||||
set_parents();
|
||||
@@ -3511,6 +3821,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this));
|
||||
}
|
||||
|
||||
// passed iterators must belong to arrays
|
||||
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array()))
|
||||
{
|
||||
JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to arrays", this));
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);
|
||||
}
|
||||
@@ -3597,17 +3913,54 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object));
|
||||
}
|
||||
|
||||
update_members(first, last, merge_objects, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief an object @ref update_members_iteratively or @ref
|
||||
/// merge_patch_iteratively is merging into, and the members still to merge
|
||||
struct merge_frame
|
||||
{
|
||||
merge_frame(basic_json* target_, const_iterator position_, const_iterator last_) noexcept
|
||||
: target(target_), position(std::move(position_)), last(std::move(last_))
|
||||
{}
|
||||
|
||||
basic_json* target;
|
||||
const_iterator position;
|
||||
const_iterator last;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief the members loop of @ref update, for this object and range
|
||||
|
||||
Merging a nested object calls this function again, once per nesting
|
||||
level, so a value nested deeply enough used to exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
update_members_iteratively merges what is left without the call stack.
|
||||
|
||||
@param[in] depth nesting level of this object, counted from the object
|
||||
@ref update was called on
|
||||
*/
|
||||
void update_members(const const_iterator& first, const const_iterator& last, const bool merge_objects, const std::size_t depth)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
update_members_iteratively(first, last);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = first; it != last; ++it)
|
||||
{
|
||||
if (merge_objects && it.value().is_object())
|
||||
{
|
||||
auto it2 = m_data.m_value.object->find(it.key());
|
||||
const auto it2 = m_data.m_value.object->find(it.key());
|
||||
// Only recurse when the existing value is itself an object.
|
||||
// Otherwise overwrite, matching the documented "all other values
|
||||
// are overwritten as usual" behavior (see #5402).
|
||||
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
it2->second.update(it.value(), true);
|
||||
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
|
||||
#if JSON_DIAGNOSTICS
|
||||
it2->second.set_parents();
|
||||
#endif
|
||||
@@ -3621,6 +3974,64 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief merge @a first to @a last into this object without the call stack
|
||||
|
||||
Does the same as @ref update_members with `merge_objects` set, keeping the
|
||||
objects whose merge was interrupted by a nested one on an explicit stack
|
||||
instead of descending into them. A nested object is still merged
|
||||
completely before the next member, in the same order as the recursive
|
||||
version. Only reached for values nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void update_members_iteratively(const_iterator first, const_iterator last)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
basic_json* target = this;
|
||||
while (true)
|
||||
{
|
||||
if (first == last)
|
||||
{
|
||||
if (stack.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// a nested object is merged: continue with its parent
|
||||
#if JSON_DIAGNOSTICS
|
||||
target->set_parents();
|
||||
#endif
|
||||
target = stack.back().target;
|
||||
first = stack.back().position;
|
||||
last = stack.back().last;
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first.value().is_object())
|
||||
{
|
||||
const auto it2 = target->m_data.m_value.object->find(first.key());
|
||||
if (it2 != target->m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
const basic_json& source = first.value();
|
||||
++first;
|
||||
stack.emplace_back(target, first, last);
|
||||
target = &it2->second;
|
||||
first = source.cbegin();
|
||||
last = source.cend();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
target->m_data.m_value.object->operator[](first.key()) = first.value();
|
||||
#if JSON_DIAGNOSTICS
|
||||
target->m_data.m_value.object->operator[](first.key()).m_parent = target;
|
||||
#endif
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief exchanges the values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/swap/
|
||||
void swap(reference other) noexcept (
|
||||
@@ -4438,7 +4849,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
static std::vector<std::uint8_t> to_cbor(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
to_cbor(j, result);
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
vector_writer(result).write_cbor(j);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4461,7 +4873,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
static std::vector<std::uint8_t> to_msgpack(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
to_msgpack(j, result);
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
vector_writer(result).write_msgpack(j);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4486,7 +4899,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
const bool use_type = false)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
to_ubjson(j, result, use_size, use_type);
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
vector_writer(result).write_ubjson(j, use_size, use_type);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4514,7 +4928,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
const bjdata_version_t version = bjdata_version_t::draft2)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
to_bjdata(j, result, use_size, use_type, version);
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
vector_writer(result).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4541,7 +4956,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
static std::vector<std::uint8_t> to_bson(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
to_bson(j, result);
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
vector_writer(result).write_bson(j);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4559,6 +4975,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
binary_writer<char>(o).write_bson(j);
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
vector_writer(result).write_bon8(j);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o).write_bon8(j);
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
||||
{
|
||||
binary_writer<char>(o).write_bon8(j);
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in CBOR format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
|
||||
template<typename InputType>
|
||||
@@ -4792,6 +5232,43 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in BON8 format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bon8/
|
||||
template<typename InputType>
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
static basic_json from_bon8(InputType&& i,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
{
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in BON8 format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bon8/
|
||||
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
static basic_json from_bon8(IteratorType first, SentinelType last,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
{
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in BSON format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
|
||||
template<typename InputType>
|
||||
@@ -5332,34 +5809,139 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
// first pass: traverse this object's elements
|
||||
// first pass: record, for every source key, whether it is
|
||||
// common to both objects (in source's iteration order) or
|
||||
// was deleted (i.e., in source but not in target) -- this is
|
||||
// a by-product of the target.find() call already needed to
|
||||
// tell the two cases apart, so it adds no extra lookups. The
|
||||
// "remove" ops themselves are emitted later, interleaved
|
||||
// with the recursive per-key diffs in the fast path below,
|
||||
// to match source's original iteration order (as the
|
||||
// original, pre-reordering-aware implementation did) instead
|
||||
// of grouping all removes before all recursive diffs.
|
||||
std::vector<typename object_t::key_type> common_keys_source_order;
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
// escape the key name to be used in a JSON patch
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
|
||||
if (target.find(it.key()) != target.end())
|
||||
{
|
||||
// recursive call to compare object values at key it
|
||||
auto temp_diff = diff(it.value(), target[it.key()], path_key);
|
||||
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
||||
common_keys_source_order.push_back(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: find keys that were added (i.e., in target but
|
||||
// not in source), and record the keys common to both, in
|
||||
// target's iteration order -- again a by-product of the
|
||||
// source.find() call already needed to detect added keys. At
|
||||
// the same time, determine whether every added key comes
|
||||
// after every common key in target's order (a precondition
|
||||
// for the fast path below, which only ever appends new keys
|
||||
// at the very end): for an object_t whose iteration order is
|
||||
// a pure function of the key set (e.g. the default std::map,
|
||||
// which always iterates in sorted key order), the order
|
||||
// check further below is always true and this whole
|
||||
// mechanism is effectively a no-op; it only matters for a
|
||||
// reorderable object_t such as the one backing `ordered_json`.
|
||||
// patch ops for keys that were added (i.e., in target but not
|
||||
// in source); built here so the fast path below can reuse
|
||||
// them without a second source.find() per target key. Only
|
||||
// used by the fast path -- the slow (reordering) path
|
||||
// rebuilds "add" ops for every key itself.
|
||||
std::vector<typename object_t::key_type> common_keys_target_order;
|
||||
basic_json added_ops(value_t::array);
|
||||
bool new_keys_form_suffix = true;
|
||||
bool seen_new_key = false;
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
{
|
||||
if (source.find(it.key()) == source.end())
|
||||
{
|
||||
seen_new_key = true;
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
added_ops.push_back(
|
||||
{
|
||||
{"op", "add"}, {"path", path_key},
|
||||
{"value", it.value()}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// found a key that is not in o -> remove it
|
||||
common_keys_target_order.push_back(it.key());
|
||||
if (seen_new_key)
|
||||
{
|
||||
new_keys_form_suffix = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
|
||||
{
|
||||
// fast path: order of common keys already matches (or the
|
||||
// object_t's iteration order does not depend on
|
||||
// insertion history), so a plain per-key recursive diff
|
||||
// is correct and minimal, as before. common_keys_source_order
|
||||
// is, by construction, the subsequence of source's keys
|
||||
// that are common to both objects, in source's iteration
|
||||
// order -- so it can be walked in lockstep with `source`
|
||||
// using a cheap key comparison instead of another lookup.
|
||||
// Deleted keys (those source keys not in common_keys_source_order)
|
||||
// are interleaved here too, in source's original order, to
|
||||
// match the historical (pre-reordering-aware) output order.
|
||||
auto common_it = common_keys_source_order.cbegin();
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
if (common_it != common_keys_source_order.cend() && it.key() == *common_it)
|
||||
{
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
auto temp_diff = diff(it.value(), target[it.key()], path_key);
|
||||
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
||||
++common_it;
|
||||
}
|
||||
else
|
||||
{
|
||||
// found a key that is not in target -> remove it
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"}, {"path", path_key}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// append the "add" ops for brand-new keys collected above
|
||||
// during the pass over target -- no second source.find()
|
||||
// per target key needed
|
||||
result.insert(result.end(), added_ops.begin(), added_ops.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow path: the common keys are in a different relative
|
||||
// order in source and target (only possible for a
|
||||
// reorderable object_t like ordered_map). Building a
|
||||
// minimal reordering patch is a nontrivial (LCS-like)
|
||||
// problem; instead, remove every source key -- both
|
||||
// deleted keys (which must be removed regardless) and
|
||||
// common keys (removed so they can be re-added in
|
||||
// target's order) -- and re-add every key that should
|
||||
// remain, with its final target value, in target's
|
||||
// order. basic_json::patch()'s "add" operation on an
|
||||
// object uses operator[], which appends at the end for a
|
||||
// vector-backed insertion-ordered map when the key does
|
||||
// not already exist -- so removing a key and then adding
|
||||
// it moves it to the end, fixing its position.
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"}, {"path", path_key}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: traverse other object's elements
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
{
|
||||
if (source.find(it.key()) == source.end())
|
||||
// add every key that is either common (just removed
|
||||
// above) or brand new, in target's iteration order, so
|
||||
// that the final order after applying the patch matches
|
||||
// target exactly
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
{
|
||||
// found a key that is not in this -> add it
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
result.push_back(
|
||||
{
|
||||
@@ -5405,9 +5987,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @brief applies a JSON Merge Patch
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/merge_patch/
|
||||
void merge_patch(const basic_json& apply_patch)
|
||||
{
|
||||
apply_merge_patch(apply_patch, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief @ref merge_patch, for a patch at nesting level @a depth
|
||||
|
||||
Applying a nested object calls this function again, once per nesting
|
||||
level, so a patch nested deeply enough used to exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
merge_patch_iteratively applies what is left without the call stack.
|
||||
*/
|
||||
void apply_merge_patch(const basic_json& apply_patch, const std::size_t depth)
|
||||
{
|
||||
if (apply_patch.is_object())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
merge_patch_iteratively(apply_patch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_object())
|
||||
{
|
||||
*this = object();
|
||||
@@ -5420,7 +6023,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
else
|
||||
{
|
||||
operator[](it.key()).merge_patch(it.value());
|
||||
operator[](it.key()).apply_merge_patch(it.value(), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5430,6 +6033,62 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief apply @a apply_patch to this value without the call stack
|
||||
|
||||
Does the same as @ref merge_patch, keeping the objects being patched on an
|
||||
explicit stack instead of descending into them. A nested object is still
|
||||
patched completely before the next member, in the same order as the
|
||||
recursive version. Only reached for patches nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void merge_patch_iteratively(const basic_json& apply_patch)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
// patch `target` with `patch`, or start patching it member by member
|
||||
const auto apply = [&stack](basic_json & target, const basic_json & patch)
|
||||
{
|
||||
if (patch.is_object())
|
||||
{
|
||||
if (!target.is_object())
|
||||
{
|
||||
target = basic_json::object();
|
||||
}
|
||||
stack.emplace_back(&target, patch.cbegin(), patch.cend());
|
||||
}
|
||||
else
|
||||
{
|
||||
target = patch;
|
||||
}
|
||||
};
|
||||
|
||||
apply(*this, apply_patch);
|
||||
while (!stack.empty())
|
||||
{
|
||||
// a copy, as applying a member below can reallocate the stack;
|
||||
// the frame itself is only changed through stack.back()
|
||||
const merge_frame frame = stack.back();
|
||||
if (frame.position == frame.last)
|
||||
{
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const const_iterator member = frame.position;
|
||||
++stack.back().position;
|
||||
if (member.value().is_null())
|
||||
{
|
||||
frame.target->erase(member.key());
|
||||
}
|
||||
else
|
||||
{
|
||||
apply(frame.target->operator[](member.key()), member.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
@@ -215,6 +215,126 @@
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_dp -->
|
||||
<Type Name="nlohmann::json_abi_dp::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_dp::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_dp_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_dp_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_dp_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_bics -->
|
||||
<Type Name="nlohmann::json_abi_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_ldvcmp -->
|
||||
<Type Name="nlohmann::json_abi_diag_ldvcmp::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp::detail::value_t::null">null</DisplayString>
|
||||
@@ -275,4 +395,604 @@
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_dp -->
|
||||
<Type Name="nlohmann::json_abi_diag_dp::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_dp::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_dp_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_diag_dp_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_dp_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_bics -->
|
||||
<Type Name="nlohmann::json_abi_diag_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_diag_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_ldvcmp_dp -->
|
||||
<Type Name="nlohmann::json_abi_ldvcmp_dp::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_ldvcmp_dp::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_ldvcmp_dp_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_ldvcmp_dp_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_ldvcmp_dp_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_ldvcmp_bics -->
|
||||
<Type Name="nlohmann::json_abi_ldvcmp_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_ldvcmp_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_ldvcmp_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_ldvcmp_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_ldvcmp_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_dp_bics -->
|
||||
<Type Name="nlohmann::json_abi_dp_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_dp_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_dp_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_dp_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_dp_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp -->
|
||||
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_ldvcmp_dp::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_bics -->
|
||||
<Type Name="nlohmann::json_abi_diag_ldvcmp_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_ldvcmp_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_dp_bics -->
|
||||
<Type Name="nlohmann::json_abi_diag_dp_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_dp_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_dp_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_diag_dp_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_dp_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_ldvcmp_dp_bics -->
|
||||
<Type Name="nlohmann::json_abi_ldvcmp_dp_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_ldvcmp_dp_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp_bics -->
|
||||
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp_bics::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_ldvcmp_dp_bics::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0 -->
|
||||
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::basic_json<*>">
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
||||
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">
|
||||
*(m_data.m_value.object),view(simple)
|
||||
</ExpandedItem>
|
||||
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">
|
||||
*(m_data.m_value.array),view(simple)
|
||||
</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- Skip the pair first/second members in the treeview while traversing a map.
|
||||
Only works in VS 2015 Update 2 and beyond using the new visualization -->
|
||||
<Type Name="std::pair<*, nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::basic_json<*>>" IncludeView="MapHelper">
|
||||
<DisplayString>{second}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>second</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
||||
|
||||
+2510
-478
File diff suppressed because it is too large
Load Diff
@@ -52,6 +52,10 @@
|
||||
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -70,20 +74,27 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#endif
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS)
|
||||
|
||||
// Construct the namespace version component
|
||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
|
||||
@@ -75,7 +75,12 @@ target_compile_options(test_main PUBLIC
|
||||
# is annotated JSON_HEDLEY_NO_RETURN (it always throws), which
|
||||
# makes MSVC flag the code following its call in binary_reader.hpp
|
||||
# as unreachable for that instantiation, in both Debug and Release
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702>
|
||||
# Disable warning C4503: decorated name length exceeded, name was truncated; the deep
|
||||
# copy support added for #5387 pushes the mangled name of
|
||||
# std::allocator_traits<...>::construct for the custom-base-class
|
||||
# test's map type past VS2015's limit. The name is only used for
|
||||
# debug info, so truncation does not affect the build.
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702;/wd4503>
|
||||
# https://github.com/nlohmann/json/issues/1114
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
|
||||
|
||||
@@ -104,7 +109,7 @@ endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
# avoid stack overflow, see https://github.com/nlohmann/json/issues/2955
|
||||
json_test_set_test_options("test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
json_test_set_test_options("test-bon8;test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
endif()
|
||||
|
||||
# disable exceptions for test-disabled_exceptions
|
||||
|
||||
+4
-1
@@ -10,7 +10,7 @@ CXXFLAGS += -std=c++11
|
||||
CPPFLAGS += -I ../single_include
|
||||
|
||||
FUZZER_ENGINE = src/fuzzer-driver_afl.cpp
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer parse_bon8_fuzzer
|
||||
fuzzers: $(FUZZERS)
|
||||
|
||||
parse_afl_fuzzer:
|
||||
@@ -30,3 +30,6 @@ parse_ubjson_fuzzer:
|
||||
|
||||
parse_bjdata_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bjdata.cpp -o $@
|
||||
|
||||
parse_bon8_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bon8.cpp -o $@
|
||||
|
||||
@@ -14,6 +14,20 @@ add_test(
|
||||
NAME test-abi_config_noversion
|
||||
COMMAND abi_config_noversion ${DOCTEST_TEST_FILTER})
|
||||
|
||||
# test default and no version namespace with all ABI tags enabled, so the
|
||||
# expected tag order is checked regardless of the JSON_* CMake options
|
||||
foreach(test default noversion)
|
||||
add_executable(abi_config_${test}_all_tags ${test}.cpp)
|
||||
target_compile_definitions(abi_config_${test}_all_tags PRIVATE
|
||||
JSON_DIAGNOSTICS=1
|
||||
JSON_DIAGNOSTIC_POSITIONS=1
|
||||
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)
|
||||
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
||||
add_test(
|
||||
NAME test-abi_config_${test}_all_tags
|
||||
COMMAND abi_config_${test}_all_tags ${DOCTEST_TEST_FILTER})
|
||||
endforeach()
|
||||
|
||||
# test custom namespace
|
||||
add_executable(abi_config_custom custom.cpp)
|
||||
target_link_libraries(abi_config_custom PRIVATE abi_compat_main)
|
||||
|
||||
@@ -24,12 +24,16 @@ TEST_CASE("default namespace")
|
||||
expected += "_diag";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
expected += "_dp";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
expected += "_bics";
|
||||
#endif
|
||||
|
||||
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
||||
|
||||
@@ -25,12 +25,16 @@ TEST_CASE("default namespace without version component")
|
||||
expected += "_diag";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
expected += "_dp";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
expected += "_bics";
|
||||
#endif
|
||||
|
||||
expected += "::basic_json";
|
||||
|
||||
@@ -273,7 +273,8 @@ enum class binary_format
|
||||
ubjson_optimized,
|
||||
bjdata,
|
||||
bjdata_optimized,
|
||||
bson
|
||||
bson,
|
||||
bon8
|
||||
};
|
||||
|
||||
static std::vector<std::uint8_t> to_binary(const json& j, const binary_format format)
|
||||
@@ -292,6 +293,8 @@ static std::vector<std::uint8_t> to_binary(const json& j, const binary_format fo
|
||||
return json::to_bjdata(j);
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::to_bjdata(j, true, true);
|
||||
case binary_format::bon8:
|
||||
return json::to_bon8(j);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::to_bson(j);
|
||||
@@ -312,6 +315,8 @@ static json from_binary(const std::vector<std::uint8_t>& bytes, const binary_for
|
||||
case binary_format::bjdata:
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::from_bjdata(bytes);
|
||||
case binary_format::bon8:
|
||||
return json::from_bon8(bytes);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::from_bson(bytes);
|
||||
@@ -332,6 +337,8 @@ static json from_binary(std::FILE* file, const binary_format format)
|
||||
case binary_format::bjdata:
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::from_bjdata(file);
|
||||
case binary_format::bon8:
|
||||
return json::from_bon8(file);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::from_bson(file);
|
||||
@@ -406,6 +413,10 @@ BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / canada, TEST_DATA_DIRECTORY "/nativ
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8);
|
||||
// BSON requires an object at the top level, so the array-rooted test files
|
||||
// (jeopardy and the regression files) cannot be captured here
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bson);
|
||||
@@ -449,6 +460,8 @@ BENCHMARK_CAPTURE(FromBinaryFile, cbor / twitter, TEST_DATA_DIRECTORY "/nativejs
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -529,18 +542,21 @@ BENCHMARK_CAPTURE(FromBinaryShape, nested / msgpack, make_nested, binary_format:
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / ubjson, make_nested, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bjdata, make_nested, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bson, make_nested, binary_format::bson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bon8, make_nested, binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / cbor, make_containers, binary_format::cbor);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / msgpack, make_containers, binary_format::msgpack);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson, make_containers, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_containers, binary_format::ubjson_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bon8, make_containers, binary_format::bon8);
|
||||
// BSON names every array element, so a large array measures key generation
|
||||
// rather than scalar decoding and is left out here
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / msgpack, make_scalars, binary_format::msgpack);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / ubjson, make_scalars, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bjdata, make_scalars, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bon8, make_scalars, binary_format::bon8);
|
||||
|
||||
/*!
|
||||
@brief parse an indefinite-length CBOR string
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# Fuzz testing
|
||||
|
||||
Each parser of the library (JSON, BJData, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently,
|
||||
Each parser of the library (JSON, BJData, BON8, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently,
|
||||
[libFuzzer](https://llvm.org/docs/LibFuzzer.html) and [afl++](https://github.com/AFLplusplus/AFLplusplus) are supported.
|
||||
|
||||
## Corpus creation
|
||||
@@ -10,11 +10,11 @@ directory with some simple input files that cover several features of the parser
|
||||
for mutations.
|
||||
|
||||
```shell
|
||||
TEST_DATA_VERSION=3.1.0
|
||||
TEST_DATA_VERSION=3.2.0
|
||||
wget https://github.com/nlohmann/json_test_data/archive/refs/tags/v$TEST_DATA_VERSION.zip
|
||||
unzip v$TEST_DATA_VERSION.zip
|
||||
rm v$TEST_DATA_VERSION.zip
|
||||
for FORMAT in json bjdata bson cbor msgpack ubjson
|
||||
for FORMAT in json bjdata bon8 bson cbor msgpack ubjson
|
||||
do
|
||||
rm -fr corpus_$FORMAT
|
||||
mkdir corpus_$FORMAT
|
||||
|
||||
@@ -21,16 +21,50 @@ array data, it performs the following steps:
|
||||
- j4 = from_bjdata(vec3)
|
||||
- assert(j1 == j4)
|
||||
|
||||
Re-serializing j2/j3/j4 with the same use_size/use_type settings is checked
|
||||
for value-stability rather than byte-exact stability: from_bjdata(to_bjdata(j2))
|
||||
must equal j2 (and likewise for j3, j4). Byte-exact stability does not hold in
|
||||
general, because a BJData value can lose type fidelity across a round trip
|
||||
(e.g. a binary_t value serialized without the optimized "$U#" array header is
|
||||
parsed back as a plain array of numbers, see #5398 and the discussion on
|
||||
PR #5494) - the numeric value is preserved, but the writer's smallest-type
|
||||
selection for the now-plain numbers may legitimately pick a different, but
|
||||
equally valid, single-byte type marker than the dedicated binary-data writer
|
||||
would have. Both encodings are valid BJData and both decode to the same
|
||||
value, so this is not treated as a round-trip failure here.
|
||||
|
||||
"Value-stable" is checked by comparing dump()s rather than with operator==
|
||||
directly: a BJData/UBJSON payload can decode to a non-finite double (NaN or
|
||||
+-Infinity), and IEEE 754 NaN is never equal to itself, so operator== would
|
||||
report two structurally-identical trees as different whenever a NaN is
|
||||
involved -- not a round-trip bug, just NaN's ordinary (non-)reflexivity.
|
||||
dump() serializes any non-finite double the same deterministic way (as JSON
|
||||
`null`, since JSON itself cannot represent NaN/Infinity), so comparing
|
||||
dumps is stable under exactly the same values that break operator==.
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// value-stable comparison for the round-trip checks below; see the note
|
||||
// above on why this compares dump()s rather than the json values directly
|
||||
static bool is_value_stable(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump() == rhs.dump();
|
||||
}
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
@@ -56,10 +90,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
json const j3 = json::from_bjdata(vec3);
|
||||
json const j4 = json::from_bjdata(vec4);
|
||||
|
||||
// serializations must match
|
||||
assert(json::to_bjdata(j2, false, false) == vec2);
|
||||
assert(json::to_bjdata(j3, true, false) == vec3);
|
||||
assert(json::to_bjdata(j4, true, true) == vec4);
|
||||
// re-serializing must be value-stable (see the notes above on
|
||||
// why byte-exact stability is not guaranteed in general, and
|
||||
// why this compares dump()s rather than the values directly)
|
||||
assert(is_value_stable(json::from_bjdata(json::to_bjdata(j2, false, false)), j2));
|
||||
assert(is_value_stable(json::from_bjdata(json::to_bjdata(j3, true, false)), j3));
|
||||
assert(is_value_stable(json::from_bjdata(json::to_bjdata(j4, true, true)), j4));
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
/*
|
||||
This file implements a parser test suitable for fuzz testing. Given a byte
|
||||
array data, it performs the following steps:
|
||||
|
||||
- j1 = from_bon8(data)
|
||||
- vec = to_bon8(j1)
|
||||
- j2 = from_bon8(vec)
|
||||
- assert(j1 == j2)
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_bon8(vec1);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2: round trip
|
||||
std::vector<uint8_t> const vec2 = json::to_bon8(j1);
|
||||
|
||||
// parse serialization
|
||||
json const j2 = json::from_bon8(vec2);
|
||||
|
||||
// serializations must match
|
||||
assert(json::to_bon8(j2) == vec2);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parsing a BON8 serialization must not fail
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
// type errors can occur during parsing, too
|
||||
}
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// out of range errors may happen if provided sizes are excessive
|
||||
}
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -19,10 +19,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -19,10 +19,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -20,10 +20,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -19,10 +19,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -25,10 +25,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Standalone compile-and-run check for the JSON_SKIP_LIBRARY_VERSION_CHECK
|
||||
// configuration macro, which (per #5423) was never exercised anywhere in the
|
||||
// test matrix.
|
||||
//
|
||||
// include/nlohmann/detail/abi_macros.hpp normally emits a #warning if
|
||||
// NLOHMANN_JSON_VERSION_MAJOR/MINOR/PATCH are already defined (as they would
|
||||
// be by an earlier inclusion of a different version of the library) with
|
||||
// values that mismatch the version about to be defined -- unless
|
||||
// JSON_SKIP_LIBRARY_VERSION_CHECK is defined, in which case the check (and
|
||||
// that #warning) is skipped.
|
||||
//
|
||||
// This file deliberately is not named tests/src/unit-*.cpp: it is compiled
|
||||
// directly (with a modest, non-strict warning set) by the dedicated
|
||||
// ci_test_skiplibraryversioncheck target in cmake/ci.cmake, rather than being
|
||||
// folded into the library's own -Weverything/-Werror unit test matrix. That
|
||||
// is because the scenario simulated here -- mixing two different, already
|
||||
// differently-versioned inclusions of the library in one translation unit --
|
||||
// unavoidably also triggers the *compiler's own* "macro redefined" warning,
|
||||
// independent of (and unaffected by) JSON_SKIP_LIBRARY_VERSION_CHECK, which
|
||||
// only ever silences the library's own #warning. Building this file under
|
||||
// -Weverything -Werror would therefore fail for a reason unrelated to the
|
||||
// macro under test.
|
||||
#define NLOHMANN_JSON_VERSION_MAJOR 0
|
||||
#define NLOHMANN_JSON_VERSION_MINOR 0
|
||||
#define NLOHMANN_JSON_VERSION_PATCH 0
|
||||
|
||||
#define JSON_SKIP_LIBRARY_VERSION_CHECK 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
// reaching this point at all already proves that the mismatched,
|
||||
// pre-defined version macros above did not stop compilation -- which is
|
||||
// exactly what JSON_SKIP_LIBRARY_VERSION_CHECK is for. The library must
|
||||
// also still be fully usable.
|
||||
const nlohmann::json j = {{"a", 1}, {"b", {1, 2, 3}}};
|
||||
if (j.dump() != "{\"a\":1,\"b\":[1,2,3]}")
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// include/nlohmann/detail/abi_macros.hpp unconditionally (re)defines the
|
||||
// version macros to the library's real, current version right after the
|
||||
// (here, skipped) mismatch check, regardless of the deliberately wrong
|
||||
// stand-in values defined above.
|
||||
if (NLOHMANN_JSON_VERSION_MAJOR == 0 && NLOHMANN_JSON_VERSION_MINOR == 0 && NLOHMANN_JSON_VERSION_PATCH == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -216,6 +216,57 @@ TEST_CASE("controlled bad_alloc")
|
||||
CHECK_THROWS_AS(my_json(s), std::bad_alloc&);
|
||||
next_construct_fails = false;
|
||||
}
|
||||
|
||||
SECTION("basic_json(const basic_json&) of a deeply nested value (#5387)")
|
||||
{
|
||||
// Copying a value nested deeper than the descent bound builds the
|
||||
// copy from the top down: every value whose own copy has not been
|
||||
// made yet stays a null value until it is. Failing an allocation
|
||||
// part-way through is what proves such a half-built copy can still
|
||||
// be destroyed.
|
||||
//
|
||||
// Which path the failure lands in depends on the build: the first
|
||||
// allocation of a copy belongs to the outermost level, so here it
|
||||
// is the descending one. Built with JSON_NO_THREAD_LOCAL - as the
|
||||
// ci_test_no_thread_local target builds the whole suite - no
|
||||
// descent is made at all and the very same failure lands in the
|
||||
// iterative path instead, part-way through its worklist.
|
||||
const auto check_deep_copy = [](bool objects)
|
||||
{
|
||||
CAPTURE(objects);
|
||||
|
||||
next_construct_fails = false;
|
||||
|
||||
// deeper than the 128 levels the copy constructor descends into
|
||||
const std::size_t depth = 300;
|
||||
|
||||
my_json j = 1;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (objects)
|
||||
{
|
||||
my_json wrapper = my_json::object();
|
||||
wrapper["a"] = std::move(j);
|
||||
j = std::move(wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
j = my_json::array({std::move(j)});
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
CHECK_NOTHROW(my_json(j));
|
||||
|
||||
next_construct_fails = true;
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
CHECK_THROWS_AS(my_json(j), std::bad_alloc&);
|
||||
next_construct_fails = false;
|
||||
};
|
||||
|
||||
check_deep_copy(false);
|
||||
check_deep_copy(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ TEST_CASE("alternative string type")
|
||||
|
||||
CHECK(alt_json::from_cbor(alt_json::to_cbor(doc)) == doc);
|
||||
CHECK(alt_json::from_msgpack(alt_json::to_msgpack(doc)) == doc);
|
||||
CHECK(alt_json::from_bon8(alt_json::to_bon8(doc)) == doc);
|
||||
// BSON is not covered: it additionally needs string_t::find(value_type),
|
||||
// which alt_string does not provide
|
||||
CHECK(alt_json::from_ubjson(alt_json::to_ubjson(doc)) == doc);
|
||||
|
||||
@@ -25,6 +25,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson(j).size();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -36,6 +37,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 1112030);
|
||||
CHECK(bjdata_2_size == 1224148);
|
||||
CHECK(bjdata_3_size == 1224148);
|
||||
CHECK(bon8_size == 1055792);
|
||||
CHECK(bson_size == 1794522);
|
||||
CHECK(cbor_size == 1055552);
|
||||
CHECK(msgpack_size == 1056145);
|
||||
@@ -47,6 +49,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.199));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.563));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.563));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(50.509));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.849));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.497));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.526));
|
||||
@@ -64,6 +67,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson(j).size();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -75,6 +79,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 425342);
|
||||
CHECK(bjdata_2_size == 429970);
|
||||
CHECK(bjdata_3_size == 429970);
|
||||
CHECK(bon8_size == 391396);
|
||||
CHECK(bson_size == 444568);
|
||||
CHECK(cbor_size == 402814);
|
||||
CHECK(msgpack_size == 401510);
|
||||
@@ -86,6 +91,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(91.097));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(92.089));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(92.089));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(83.828));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.215));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(86.273));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(85.993));
|
||||
@@ -103,6 +109,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson(j).size();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -114,6 +121,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 390781);
|
||||
CHECK(bjdata_2_size == 433557);
|
||||
CHECK(bjdata_3_size == 432964);
|
||||
CHECK(bon8_size == 317879);
|
||||
CHECK(bson_size == 479430);
|
||||
CHECK(cbor_size == 342373);
|
||||
CHECK(msgpack_size == 342473);
|
||||
@@ -125,6 +133,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(78.109));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(86.659));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(86.541));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(63.538));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.828));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(68.433));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(68.453));
|
||||
@@ -142,6 +151,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
const auto bson_size = json::to_bson({{"", j}}).size(); // wrap array in object for BSON
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -153,6 +163,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 50710965);
|
||||
CHECK(bjdata_2_size == 51144830);
|
||||
CHECK(bjdata_3_size == 51144830);
|
||||
CHECK(bon8_size == 45942080);
|
||||
CHECK(bson_size == 56008520);
|
||||
CHECK(cbor_size == 46187320);
|
||||
CHECK(msgpack_size == 46158575);
|
||||
@@ -164,6 +175,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(96.576));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(97.402));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(97.402));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(87.494));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(106.665));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.961));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.906));
|
||||
@@ -181,6 +193,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||
const auto bon8_size = json::to_bon8(j).size();
|
||||
// BSON cannot process the file as it contains code point U+0000
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -192,6 +205,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 148695);
|
||||
CHECK(bjdata_2_size == 150569);
|
||||
CHECK(bjdata_3_size == 150569);
|
||||
CHECK(bon8_size == 144477);
|
||||
CHECK(cbor_size == 147095);
|
||||
CHECK(msgpack_size == 147017);
|
||||
CHECK(ubjson_1_size == 148695);
|
||||
@@ -202,6 +216,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(88.153));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(89.264));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(89.264));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(85.653));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.205));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.158));
|
||||
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(88.153));
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// a spread of values exercising every writer path: scalars of each width, the
|
||||
// float paths, strings, binary, and containers big enough to reallocate
|
||||
std::vector<json> test_values()
|
||||
{
|
||||
json big_array = json::array();
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
{
|
||||
big_array.push_back(i);
|
||||
}
|
||||
|
||||
json big_object = json::object();
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
big_object[std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
return
|
||||
{
|
||||
json(nullptr), json(true), json(false),
|
||||
json(0), json(-1), json(255), json(-129), json(65535), json(-32769),
|
||||
json(4294967295U), json(-2147483649LL), json(18446744073709551615ULL),
|
||||
json(0.0), json(-0.5), json(3.1415926535897932),
|
||||
json(""), json("hello"), json(std::string(1000, 'x')),
|
||||
json::binary({0x00, 0x01, 0x02}, 42),
|
||||
json::array(), json::object(),
|
||||
json::array({1, 2, 3}), json({{"a", 1}, {"b", nullptr}}),
|
||||
json({{"nested", {{"deep", json::array({1, "two", 3.0, nullptr})}}}}),
|
||||
big_array, big_object
|
||||
};
|
||||
}
|
||||
|
||||
// BON8 has no integers above the int64 range, so to_bon8() rejects them
|
||||
bool bon8_representable(const json& j)
|
||||
{
|
||||
return !j.is_number_unsigned() || j.get<std::uint64_t>() <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)());
|
||||
}
|
||||
|
||||
// values to_bson() accepts: the document must be an object
|
||||
std::vector<json> bson_values()
|
||||
{
|
||||
json big_object = json::object();
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
big_object[std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
return
|
||||
{
|
||||
json::object(),
|
||||
json({{"a", 1}, {"b", nullptr}, {"c", true}, {"d", 2.5}, {"e", "text"}}),
|
||||
json({{"arr", json::array({1, 2, 3})}, {"obj", {{"k", "v"}}}}),
|
||||
big_object
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The vector-returning to_*(j) overloads write through the non-virtual
|
||||
// output_vector_sink, while to_*(j, adapter) goes through output_adapter_sink.
|
||||
// The two are separate code paths that must stay byte-for-byte identical; these
|
||||
// checks fail if either overload is ever changed without the other.
|
||||
TEST_CASE("binary writer output sinks")
|
||||
{
|
||||
SECTION("vector sink and adapter sink agree")
|
||||
{
|
||||
// note: no SUBCASE inside these loops - doctest keys subcases by
|
||||
// name/file/line, so a subcase in a loop body would only ever run for
|
||||
// the first iteration
|
||||
for (const auto& j : test_values())
|
||||
{
|
||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
||||
|
||||
std::vector<std::uint8_t> cbor;
|
||||
json::to_cbor(j, cbor);
|
||||
CHECK(json::to_cbor(j) == cbor);
|
||||
|
||||
std::vector<std::uint8_t> msgpack;
|
||||
json::to_msgpack(j, msgpack);
|
||||
CHECK(json::to_msgpack(j) == msgpack);
|
||||
|
||||
if (bon8_representable(j))
|
||||
{
|
||||
std::vector<std::uint8_t> bon8;
|
||||
json::to_bon8(j, bon8);
|
||||
CHECK(json::to_bon8(j) == bon8);
|
||||
}
|
||||
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
for (const bool use_type :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
if (use_type && !use_size)
|
||||
{
|
||||
continue; // not a supported combination
|
||||
}
|
||||
CAPTURE(use_size);
|
||||
CAPTURE(use_type);
|
||||
std::vector<std::uint8_t> ubjson;
|
||||
json::to_ubjson(j, ubjson, use_size, use_type);
|
||||
CHECK(json::to_ubjson(j, use_size, use_type) == ubjson);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto version :
|
||||
{
|
||||
json::bjdata_version_t::draft2, json::bjdata_version_t::draft3
|
||||
})
|
||||
{
|
||||
std::vector<std::uint8_t> bjdata;
|
||||
json::to_bjdata(j, bjdata, false, false, version);
|
||||
CHECK(json::to_bjdata(j, false, false, version) == bjdata);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& j : bson_values())
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
std::vector<std::uint8_t> bson;
|
||||
json::to_bson(j, bson);
|
||||
CHECK(json::to_bson(j) == bson);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("the char adapter produces the same bytes")
|
||||
{
|
||||
for (const auto& j : test_values())
|
||||
{
|
||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
||||
|
||||
const std::vector<std::uint8_t> expected = json::to_cbor(j);
|
||||
std::vector<char> as_char;
|
||||
json::to_cbor(j, as_char);
|
||||
|
||||
REQUIRE(as_char.size() == expected.size());
|
||||
std::vector<std::uint8_t> as_bytes;
|
||||
as_bytes.reserve(as_char.size());
|
||||
for (const char c : as_char)
|
||||
{
|
||||
as_bytes.push_back(static_cast<std::uint8_t>(c));
|
||||
}
|
||||
CHECK(as_bytes == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// binary_reserve_hint() is documented as a *lower* bound on the serialized size,
|
||||
// so that reserving it up front can never leave the returned vector holding
|
||||
// capacity beyond what the value actually needs.
|
||||
TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
{
|
||||
for (const auto& j : test_values())
|
||||
{
|
||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
||||
|
||||
const std::size_t hint = nlohmann::detail::binary_reserve_hint(j);
|
||||
|
||||
CHECK(hint <= json::to_cbor(j).size());
|
||||
CHECK(hint <= json::to_msgpack(j).size());
|
||||
CHECK(hint <= json::to_ubjson(j).size());
|
||||
CHECK(hint <= json::to_ubjson(j, true, true).size());
|
||||
CHECK(hint <= json::to_bjdata(j).size());
|
||||
if (bon8_representable(j))
|
||||
{
|
||||
CHECK(hint <= json::to_bon8(j).size());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& j : bson_values())
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(j) <= json::to_bson(j).size());
|
||||
}
|
||||
|
||||
SECTION("scalars get no hint")
|
||||
{
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json(nullptr)) == 0);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json(42)) == 0);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json("a string")) == 0);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::binary({0x01})) == 0);
|
||||
}
|
||||
|
||||
SECTION("containers are hinted from their element count")
|
||||
{
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::array()) == 1);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::array({1, 2, 3})) == 4);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::object()) == 1);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
||||
}
|
||||
}
|
||||
+217
-38
@@ -2586,7 +2586,12 @@ TEST_CASE("BJData")
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_d), true, true) == v_d);
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D);
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_C);
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B);
|
||||
// v_B uses the Draft-3-only 'B' marker, so it round-trips only when
|
||||
// Draft 3 is explicitly selected (see GitHub issue #5404); the
|
||||
// default Draft 2 falls back to a plain object instead, covered by
|
||||
// the "ndarray with _ArrayType_ "byte" is gated by the BJData draft
|
||||
// version" section below
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true, json::bjdata_version_t::draft3) == v_B);
|
||||
}
|
||||
|
||||
SECTION("ndarray with data not matching _ArrayType_ is written as an object")
|
||||
@@ -2599,25 +2604,25 @@ TEST_CASE("BJData")
|
||||
// that still round-trips.
|
||||
|
||||
// string data declared as a uint64 array
|
||||
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {1}}, {"_ArrayData_", {"pointer"}}});
|
||||
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {"pointer", "value"}}});
|
||||
const auto out_str = json::to_bjdata(j_str);
|
||||
CHECK(out_str.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_str) == j_str);
|
||||
|
||||
// integer data declared as a double array
|
||||
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_float = json::to_bjdata(j_float);
|
||||
CHECK(out_float.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_float) == j_float);
|
||||
|
||||
// a non-integer shape entry is likewise not treated as an ndarray
|
||||
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x"}}, {"_ArrayData_", {1}}});
|
||||
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x", 1}}, {"_ArrayData_", {1}}});
|
||||
const auto out_size = json::to_bjdata(j_size);
|
||||
CHECK(out_size.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_size) == j_size);
|
||||
|
||||
// a negative shape entry is not a usable dimension either
|
||||
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1],"_ArrayData_":[1]})");
|
||||
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1,1],"_ArrayData_":[1]})");
|
||||
const auto out_neg = json::to_bjdata(j_neg);
|
||||
CHECK(out_neg.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_neg) == j_neg);
|
||||
@@ -2629,8 +2634,10 @@ TEST_CASE("BJData")
|
||||
// the C++ API stores an int literal as number_integer, so _ArrayType_
|
||||
// names the wire type rather than the storage. Both storages have to
|
||||
// produce the same typed array for every type.
|
||||
// "byte" is checked separately below since it additionally requires
|
||||
// BJData Draft 3 to be selected explicitly (see GitHub issue #5404).
|
||||
for (const char* type :
|
||||
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char", "byte"
|
||||
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char"
|
||||
})
|
||||
{
|
||||
CAPTURE(type);
|
||||
@@ -2641,15 +2648,23 @@ TEST_CASE("BJData")
|
||||
CHECK(from_text == json::to_bjdata(json({{"_ArrayType_", type}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}})));
|
||||
}
|
||||
|
||||
{
|
||||
const std::string text = R"({"_ArrayType_":"byte","_ArraySize_":[2,3],"_ArrayData_":[1,2,3,4,5,6]})";
|
||||
const auto from_text = json::to_bjdata(json::parse(text), true, true, json::bjdata_version_t::draft3);
|
||||
CHECK(from_text.at(0) == '[');
|
||||
CHECK(from_text == json::to_bjdata(json({{"_ArrayType_", "byte"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}}),
|
||||
true, true, json::bjdata_version_t::draft3));
|
||||
}
|
||||
|
||||
// negative values under a signed type behave the same way
|
||||
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2],"_ArrayData_":[-5,7]})"));
|
||||
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2,1],"_ArrayData_":[-5,7]})"));
|
||||
CHECK(from_neg.at(0) == '[');
|
||||
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-5, 7}}})));
|
||||
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {-5, 7}}})));
|
||||
|
||||
// and so do the floating point types
|
||||
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2],"_ArrayData_":[1.5,2.5]})"));
|
||||
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2,1],"_ArrayData_":[1.5,2.5]})"));
|
||||
CHECK(from_float.at(0) == '[');
|
||||
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 2.5}}})));
|
||||
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 2.5}}})));
|
||||
}
|
||||
|
||||
SECTION("optimized ndarray (type and vector-size as 1D array)")
|
||||
@@ -2731,6 +2746,83 @@ TEST_CASE("BJData")
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
|
||||
}
|
||||
|
||||
SECTION("ndarray whose _ArrayType_ is not a string stays as object")
|
||||
{
|
||||
// the type name is looked up as a string below the annotation
|
||||
// check; a non-string _ArrayType_ cannot name a known dtype,
|
||||
// so calling get<string_t>() on it would throw type_error.302
|
||||
// instead of falling back like an unrecognized type name
|
||||
// already does (see GitHub issue #5398)
|
||||
json const j_number = json({{"_ArrayType_", 1}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_number = json::to_bjdata(j_number);
|
||||
CHECK(out_number.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_number) == j_number);
|
||||
|
||||
json const j_null = json({{"_ArrayType_", nullptr}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_null = json::to_bjdata(j_null);
|
||||
CHECK(out_null.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_null) == j_null);
|
||||
|
||||
json const j_bool = json({{"_ArrayType_", true}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_bool = json::to_bjdata(j_bool);
|
||||
CHECK(out_bool.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_bool) == j_bool);
|
||||
|
||||
json const j_array = json({{"_ArrayType_", {"uint8"}}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_array = json::to_bjdata(j_array);
|
||||
CHECK(out_array.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_array) == j_array);
|
||||
|
||||
json const j_object = json({{"_ArrayType_", {{"a", 1}}}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_object = json::to_bjdata(j_object);
|
||||
CHECK(out_object.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_object) == j_object);
|
||||
}
|
||||
|
||||
SECTION("re-serializing a value containing a plain-array-of-bytes is value-stable but not byte-stable")
|
||||
{
|
||||
// OSS-Fuzz found this input (an array whose first element is a
|
||||
// binary_t byte, followed by an object whose _ArrayType_ is
|
||||
// not a string) while exercising the fix for #5398 above: once
|
||||
// the fix stops to_bjdata() from throwing type_error.302 for
|
||||
// the third element, serialization proceeds far enough to
|
||||
// reach a pre-existing, unrelated round-trip quirk in how a
|
||||
// single-byte binary_t value is re-encoded.
|
||||
std::vector<std::uint8_t> const input
|
||||
{
|
||||
0x5b, 0x5b, 0x24, 0x42, 0x23, 0x5b, 0x69, 0x01, 0x5d, 0x5b, 0x5b, 0x5d, 0x7b, 0x55, 0x0b,
|
||||
0x5f, 0x41, 0x72, 0x72, 0x61, 0x79, 0x44, 0x61, 0x74, 0x61, 0x5f, 0x54, 0x55, 0x0b, 0x5f,
|
||||
0x41, 0x72, 0x72, 0x61, 0x79, 0x53, 0x69, 0x7a, 0x65, 0x5f, 0x5a, 0x55, 0x0b, 0x5f, 0x41,
|
||||
0x72, 0x72, 0x61, 0x79, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x7d, 0x5d
|
||||
};
|
||||
json const j1 = json::from_bjdata(input);
|
||||
|
||||
// to_bjdata() must not throw (this is what #5398 fixes)
|
||||
std::vector<std::uint8_t> vec2;
|
||||
CHECK_NOTHROW(vec2 = json::to_bjdata(j1, false, false));
|
||||
|
||||
// parsing back a plain (non-optimized) array of bytes cannot
|
||||
// recover that it used to be a binary_t: from_bjdata() has no
|
||||
// way to distinguish "array of uint8 numbers" from "array of
|
||||
// bytes" unless the compact "$U#" array header is used, so
|
||||
// the binary_t collapses into a plain JSON array
|
||||
json const j2 = json::from_bjdata(vec2);
|
||||
CHECK(j1 != j2);
|
||||
CHECK(j2 == json({{91}, json::array(), {{"_ArrayData_", true}, {"_ArraySize_", nullptr}, {"_ArrayType_", true}}}));
|
||||
|
||||
// re-serializing j2 no longer goes through the dedicated
|
||||
// binary_t writer (which always uses the 'U' marker for raw
|
||||
// bytes); the now-plain number 91 goes through the generic
|
||||
// smallest-type writer instead, which - like the rest of the
|
||||
// UBJSON/BJData writer, and unchanged by this fix - prefers
|
||||
// the 'i' (int8) marker over 'U' (uint8) for values that fit
|
||||
// both. Both markers are valid BJData and both decode back to
|
||||
// 91, so this is not byte-for-byte identical to vec2, but it
|
||||
// is value-stable: parsing it again reproduces j2 exactly.
|
||||
std::vector<std::uint8_t> const vec3 = json::to_bjdata(j2, false, false);
|
||||
CHECK(json::from_bjdata(vec3) == j2);
|
||||
}
|
||||
|
||||
SECTION("ndarray whose dimensions overflow stays as object")
|
||||
{
|
||||
// the product of the dimensions wraps around std::size_t to 0
|
||||
@@ -2743,7 +2835,7 @@ TEST_CASE("BJData")
|
||||
// a single dimension that does not fit into std::size_t is
|
||||
// rejected for the same reason (only observable where
|
||||
// std::size_t is narrower than 64 bit)
|
||||
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull}}, {"_ArrayType_", "uint8"}});
|
||||
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull, 2}}, {"_ArrayType_", "uint8"}});
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j_huge), true, true) == j_huge);
|
||||
|
||||
// a well-formed ndarray is still encoded as one
|
||||
@@ -2786,42 +2878,146 @@ TEST_CASE("BJData")
|
||||
// object encoding that still round-trips (see GitHub issue #5403)
|
||||
|
||||
// an unsigned element that does not fit uint8
|
||||
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 256}}});
|
||||
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 256}}});
|
||||
const auto out_uint8 = json::to_bjdata(j_uint8);
|
||||
CHECK(out_uint8.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_uint8) == j_uint8);
|
||||
|
||||
// a signed element that does not fit int8
|
||||
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 200}}});
|
||||
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 200}}});
|
||||
const auto out_int8 = json::to_bjdata(j_int8);
|
||||
CHECK(out_int8.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_int8) == j_int8);
|
||||
|
||||
// a negative element is likewise out of range for an
|
||||
// unsigned _ArrayType_
|
||||
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, -1}}});
|
||||
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, -1}}});
|
||||
const auto out_uint16_neg = json::to_bjdata(j_uint16_neg);
|
||||
CHECK(out_uint16_neg.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_uint16_neg) == j_uint16_neg);
|
||||
|
||||
// a double element that overflows to infinity when narrowed
|
||||
// to the "single" (float) precision named by _ArrayType_
|
||||
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 1e40}}});
|
||||
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 1e40}}});
|
||||
const auto out_single = json::to_bjdata(j_single);
|
||||
CHECK(out_single.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_single) == j_single);
|
||||
|
||||
// in-range boundary values still use the compact ndarray encoding
|
||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {0, 255}}});
|
||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, ']', 0, 255}));
|
||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {0, 255}}});
|
||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 1, ']', 0, 255}));
|
||||
|
||||
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-128, 127}}});
|
||||
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, ']', 0x80, 0x7F}));
|
||||
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {-128, 127}}});
|
||||
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, 'i', 1, ']', 0x80, 0x7F}));
|
||||
|
||||
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1.5}}});
|
||||
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, -1.5}}});
|
||||
const auto out_single_ok = json::to_bjdata(j_single_ok);
|
||||
CHECK(out_single_ok.at(0) == '[');
|
||||
CHECK(json::from_bjdata(out_single_ok) == json({1.5f}));
|
||||
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
|
||||
}
|
||||
|
||||
SECTION("ndarray that would not be read back as an annotated object stays as object")
|
||||
{
|
||||
// the reader only restores an annotated object from an ND-array
|
||||
// with at least two non-zero dimensions that is not a 1xN row
|
||||
// vector; any other shape is read back as a plain array. Writing
|
||||
// such an object as an ND-array would drop its annotation, so it
|
||||
// falls back to a plain object encoding that round-trips.
|
||||
for (const char* text :
|
||||
{
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":[]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2],"_ArrayData_":[1,2]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[1,2],"_ArrayData_":[1,2]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[0],"_ArrayData_":[]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2,0],"_ArrayData_":[]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[0,2],"_ArrayData_":[]})"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
const json j = json::parse(text);
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
const auto out = json::to_bjdata(j, use_size, use_size);
|
||||
CHECK(out.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out) == j);
|
||||
}
|
||||
}
|
||||
|
||||
// a genuine ND-array still uses the compact encoding and round-trips
|
||||
const json j_2d = json::parse(R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":[1,2]})");
|
||||
const auto out_2d = json::to_bjdata(j_2d);
|
||||
CHECK(out_2d.at(0) == '[');
|
||||
CHECK(json::from_bjdata(out_2d) == j_2d);
|
||||
}
|
||||
|
||||
SECTION("ndarray with non-array _ArrayData_ stays as object")
|
||||
{
|
||||
// the elements are written from _ArrayData_ as a flat list, so it
|
||||
// has to be an array: null has size 0, any other scalar has size 1,
|
||||
// and iterating an object visits its values, so each of these could
|
||||
// match the dimensions and be encoded as an unrelated ND-array
|
||||
for (const char* text :
|
||||
{
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":null})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":{"a":1,"b":2}})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[1],"_ArrayData_":5})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
const json j = json::parse(text);
|
||||
const auto out = json::to_bjdata(j);
|
||||
CHECK(out.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out) == j);
|
||||
}
|
||||
|
||||
// OSS-Fuzz issue 563659413: an empty binary _ArraySize_ is written
|
||||
// as a plain object and read back as an empty array, after which
|
||||
// the object with a null _ArrayData_ was encoded as an empty
|
||||
// ND-array and re-read as [], so a second round trip lost the value
|
||||
const std::vector<uint8_t> input =
|
||||
{
|
||||
'{', 'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'D', 'a', 't', 'a', '_', 'Z',
|
||||
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'T', 'y', 'p', 'e', '_', 'S', 'i', 5, 'i', 'n', 't', '1', '6',
|
||||
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'S', 'i', 'z', 'e', '_', '[', '$', 'B', '#', '[', ']', '}'
|
||||
};
|
||||
const json j1 = json::from_bjdata(input);
|
||||
const json j2 = json::from_bjdata(json::to_bjdata(j1, false, false));
|
||||
CHECK(j2 == json::parse(R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"));
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j2, false, false)) == j2);
|
||||
}
|
||||
|
||||
SECTION("ndarray with _ArrayType_ \"byte\" is gated by the BJData draft version")
|
||||
{
|
||||
// the 'B' (byte) marker used by _ArrayType_ "byte" is only defined
|
||||
// by BJData Draft 3; Draft 2 (the default) has no such marker, so
|
||||
// emitting it unconditionally produced a stream that a Draft 2
|
||||
// reader could not parse as intended (see GitHub issue #5404).
|
||||
// Two dimensions are used so that a successfully written ndarray
|
||||
// round-trips back into the annotated object (a single dimension
|
||||
// is, by the BJData ndarray convention, read back as a plain
|
||||
// binary value rather than the annotated object, same as every
|
||||
// other single-dimension ndarray of a non-"byte" type is read
|
||||
// back as a plain array instead of the annotated object).
|
||||
json const j_byte = json({{"_ArrayType_", "byte"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}});
|
||||
|
||||
// default (Draft 2): falls back to a plain object and round-trips
|
||||
const auto out_draft2 = json::to_bjdata(j_byte);
|
||||
CHECK(out_draft2.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_draft2) == j_byte);
|
||||
|
||||
// explicit Draft 2: same as the default
|
||||
const auto out_draft2_explicit = json::to_bjdata(j_byte, true, true, json::bjdata_version_t::draft2);
|
||||
CHECK(out_draft2_explicit.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_draft2_explicit) == j_byte);
|
||||
|
||||
// Draft 3 explicitly selected: still uses the compact 'B' ndarray encoding
|
||||
const auto out_draft3 = json::to_bjdata(j_byte, true, true, json::bjdata_version_t::draft3);
|
||||
CHECK(out_draft3 == std::vector<uint8_t>({'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2, 3, 4, 5, 6}));
|
||||
CHECK(json::from_bjdata(out_draft3) == j_byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2892,23 +3088,6 @@ TEST_CASE("BJData")
|
||||
CHECK(json::from_bjdata(vl, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in string (see #5529)")
|
||||
{
|
||||
// a BJData string of length 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
|
||||
std::vector<uint8_t> const v = {'S', 'i', 0x02, 0xc0, 0xae};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_bjdata(v, true, false).is_discarded());
|
||||
|
||||
// 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_bjdata(json::to_bjdata(j)) == j);
|
||||
}
|
||||
|
||||
SECTION("parse bjdata markers in ubjson")
|
||||
{
|
||||
// create a single-character string for all number types
|
||||
|
||||
@@ -0,0 +1,976 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class SaxCountdown
|
||||
{
|
||||
public:
|
||||
explicit SaxCountdown(const int count) : events_left(count)
|
||||
{}
|
||||
|
||||
bool null()
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool boolean(bool /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool string(std::string& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool binary(std::vector<std::uint8_t>& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool key(std::string& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool end_object()
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool end_array()
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
int events_left = 0;
|
||||
};
|
||||
|
||||
using bytes = std::vector<std::uint8_t>;
|
||||
|
||||
/// check that @a j is serialized to @a expected and that @a expected is read back as @a j
|
||||
void check_bon8(const json& j, const bytes& expected)
|
||||
{
|
||||
CAPTURE(j)
|
||||
CHECK(json::to_bon8(j) == expected);
|
||||
|
||||
const json read = json::from_bon8(expected);
|
||||
CHECK(read == j);
|
||||
// integers are not read back as floats and vice versa
|
||||
CHECK(read.type() == j.type());
|
||||
}
|
||||
|
||||
/// @return the string with the given bytes
|
||||
std::string str(const bytes& b)
|
||||
{
|
||||
return {b.begin(), b.end()};
|
||||
}
|
||||
|
||||
/// @return @a b followed by @a tail
|
||||
bytes concat(bytes b, const bytes& tail)
|
||||
{
|
||||
b.insert(b.end(), tail.begin(), tail.end());
|
||||
return b;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("BON8")
|
||||
{
|
||||
SECTION("individual values")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
const json j = json::value_t::discarded;
|
||||
CHECK(json::to_bon8(j).empty());
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
{
|
||||
check_bon8(nullptr, {0xFA});
|
||||
}
|
||||
|
||||
SECTION("boolean")
|
||||
{
|
||||
check_bon8(true, {0xF9});
|
||||
check_bon8(false, {0xF8});
|
||||
}
|
||||
|
||||
SECTION("integers")
|
||||
{
|
||||
// test vectors from HikoGUI (src/hikogui/codec/BON8_tests.cpp,
|
||||
// Copyright Take Vos 2021, Boost Software License 1.0), which
|
||||
// cover the first and last value of each encoding
|
||||
SECTION("positive")
|
||||
{
|
||||
check_bon8(0u, {0x90});
|
||||
check_bon8(39u, {0xB7});
|
||||
check_bon8(40u, {0xC2, 0x00});
|
||||
check_bon8(3879u, {0xDF, 0x7F});
|
||||
check_bon8(3880u, {0xE0, 0x00, 0x00});
|
||||
check_bon8(528167u, {0xEF, 0x7F, 0xFF});
|
||||
check_bon8(528168u, {0xF0, 0x00, 0x00, 0x00});
|
||||
check_bon8(67637031u, {0xF7, 0x7F, 0xFF, 0xFF});
|
||||
check_bon8(67637032u, {0x8C, 0x04, 0x08, 0x0F, 0x28});
|
||||
check_bon8(2147483647u, {0x8C, 0x7F, 0xFF, 0xFF, 0xFF});
|
||||
check_bon8(2147483648u, {0x8D, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8(9223372036854775807u, {0x8D, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("negative")
|
||||
{
|
||||
check_bon8(-1, {0xB8});
|
||||
check_bon8(-10, {0xC1});
|
||||
check_bon8(-11, {0xC2, 0xC0});
|
||||
check_bon8(-1930, {0xDF, 0xFF});
|
||||
check_bon8(-1931, {0xE0, 0xC0, 0x00});
|
||||
check_bon8(-264074, {0xEF, 0xFF, 0xFF});
|
||||
check_bon8(-264075, {0xF0, 0xC0, 0x00, 0x00});
|
||||
check_bon8(-33818506, {0xF7, 0xFF, 0xFF, 0xFF});
|
||||
check_bon8(-33818507, {0x8C, 0xFD, 0xFB, 0xF8, 0x75});
|
||||
check_bon8(-2147483648, {0x8C, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8(-2147483649, {0x8D, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF});
|
||||
check_bon8((std::numeric_limits<std::int64_t>::min)(), {0x8D, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
}
|
||||
|
||||
SECTION("values inside the ranges")
|
||||
{
|
||||
check_bon8(1u, {0x91});
|
||||
check_bon8(100u, {0xC2, 0x3C});
|
||||
check_bon8(1000u, {0xC9, 0x40});
|
||||
check_bon8(100000u, {0xE2, 0x77, 0x78});
|
||||
check_bon8(1000000u, {0xF0, 0x07, 0x33, 0x18});
|
||||
check_bon8(-9, {0xC0});
|
||||
check_bon8(-100, {0xC3, 0xD9});
|
||||
check_bon8(-100000, {0xE5, 0xFF, 0x15});
|
||||
check_bon8(-1000000, {0xF0, 0xCB, 0x3A, 0xB5});
|
||||
}
|
||||
|
||||
SECTION("every integer from -300000 to 600000")
|
||||
{
|
||||
for (std::int64_t i = -300000; i <= 600000; ++i)
|
||||
{
|
||||
const json j = i;
|
||||
const auto packed = json::to_bon8(j);
|
||||
CHECK(packed.size() == (i >= -10 && i <= 39 ? 1u : i >= -1930 && i <= 3879 ? 2u : i >= -264074 && i <= 528167 ? 3u : 4u));
|
||||
const json read = json::from_bon8(packed);
|
||||
if (read != j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CHECK(read == j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("signed values are read back as unsigned when not negative")
|
||||
{
|
||||
const json j = json::from_bon8(json::to_bon8(json(std::int64_t(1000))));
|
||||
CHECK(j.is_number_unsigned());
|
||||
CHECK(j == 1000);
|
||||
}
|
||||
|
||||
SECTION("unsigned integers above int64")
|
||||
{
|
||||
json _;
|
||||
const json j = 9223372036854775808u;
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(j), "[json.exception.out_of_range.407] integer number 9223372036854775808 cannot be represented by BON8 as it does not fit int64", json::out_of_range&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("floating-point numbers")
|
||||
{
|
||||
SECTION("one-byte values")
|
||||
{
|
||||
check_bon8(-1.0, {0xFB});
|
||||
check_bon8(0.0, {0xFC});
|
||||
check_bon8(1.0, {0xFD});
|
||||
}
|
||||
|
||||
SECTION("binary32")
|
||||
{
|
||||
check_bon8(2.0, {0x8E, 0x40, 0x00, 0x00, 0x00});
|
||||
check_bon8(0.5, {0x8E, 0x3F, 0x00, 0x00, 0x00});
|
||||
check_bon8(-1.5, {0x8E, 0xBF, 0xC0, 0x00, 0x00});
|
||||
check_bon8(3.4028234663852886e38, {0x8E, 0x7F, 0x7F, 0xFF, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("binary64")
|
||||
{
|
||||
check_bon8(100000000.1, {0x8F, 0x41, 0x97, 0xD7, 0x84, 0x00, 0x66, 0x66, 0x66});
|
||||
check_bon8(3.14159, {0x8F, 0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E});
|
||||
check_bon8(1e300, {0x8F, 0x7E, 0x37, 0xE4, 0x3C, 0x88, 0x00, 0x75, 0x9C});
|
||||
}
|
||||
|
||||
SECTION("-0.0 is written as binary32")
|
||||
{
|
||||
const json j = -0.0;
|
||||
CHECK(json::to_bon8(j) == bytes{0x8E, 0x80, 0x00, 0x00, 0x00});
|
||||
const json read = json::from_bon8(json::to_bon8(j));
|
||||
CHECK(read.get<double>() == 0.0);
|
||||
CHECK(std::signbit(read.get<double>()));
|
||||
}
|
||||
|
||||
SECTION("infinity is written as binary32")
|
||||
{
|
||||
check_bon8(std::numeric_limits<double>::infinity(), {0x8E, 0x7F, 0x80, 0x00, 0x00});
|
||||
check_bon8(-std::numeric_limits<double>::infinity(), {0x8E, 0xFF, 0x80, 0x00, 0x00});
|
||||
}
|
||||
|
||||
SECTION("NaN is written as binary32 0x7F800001")
|
||||
{
|
||||
const json j = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK(json::to_bon8(j) == bytes{0x8E, 0x7F, 0x80, 0x00, 0x01});
|
||||
CHECK(std::isnan(json::from_bon8(json::to_bon8(j)).get<double>()));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("strings")
|
||||
{
|
||||
SECTION("empty string")
|
||||
{
|
||||
check_bon8("", {0xFF});
|
||||
}
|
||||
|
||||
SECTION("ASCII")
|
||||
{
|
||||
check_bon8("a", {'a', 0xFF});
|
||||
check_bon8("This is a string.", concat(bytes{'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.'}, {0xFF}));
|
||||
check_bon8(str({0x00}), {0x00, 0xFF});
|
||||
check_bon8(str({0x7F}), {0x7F, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("multi-byte UTF-8")
|
||||
{
|
||||
check_bon8("\xC2\xA3", {0xC2, 0xA3, 0xFF}); // U+00A3
|
||||
check_bon8("\xEF\xB8\xBB", {0xEF, 0xB8, 0xBB, 0xFF}); // U+FE3B
|
||||
check_bon8("\xF0\x9F\x80\x84", {0xF0, 0x9F, 0x80, 0x84, 0xFF}); // U+1F004
|
||||
check_bon8("\xC2\x80", {0xC2, 0x80, 0xFF}); // U+0080
|
||||
check_bon8("\xDF\xBF", {0xDF, 0xBF, 0xFF}); // U+07FF
|
||||
check_bon8("\xE0\xA0\x80", {0xE0, 0xA0, 0x80, 0xFF}); // U+0800
|
||||
check_bon8("\xED\x9F\xBF", {0xED, 0x9F, 0xBF, 0xFF}); // U+D7FF
|
||||
check_bon8("\xEE\x80\x80", {0xEE, 0x80, 0x80, 0xFF}); // U+E000
|
||||
check_bon8("\xF0\x90\x80\x80", {0xF0, 0x90, 0x80, 0x80, 0xFF}); // U+10000
|
||||
check_bon8("\xF4\x8F\xBF\xBF", {0xF4, 0x8F, 0xBF, 0xBF, 0xFF}); // U+10FFFF
|
||||
check_bon8("a\xC2\xA3" "b", {'a', 0xC2, 0xA3, 'b', 0xFF});
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 cannot be written")
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0x80", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({'a', 0xC0, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xC0", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xE0, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xE0", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xED, 0xA0, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xF4, 0x90, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xF4", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xF5, 0x80, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xF5", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xC2, 'a'})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC2", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({'a', 0xE2, 0x82})), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xE2", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(json::object({{str({0xFF}), 1}})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("arrays")
|
||||
{
|
||||
check_bon8(json::array(), {0x80});
|
||||
check_bon8({false}, {0x81, 0xF8});
|
||||
check_bon8({false, nullptr}, {0x82, 0xF8, 0xFA});
|
||||
check_bon8({false, nullptr, true}, {0x83, 0xF8, 0xFA, 0xF9});
|
||||
check_bon8({false, nullptr, true, 1.0}, {0x84, 0xF8, 0xFA, 0xF9, 0xFD});
|
||||
check_bon8({false, nullptr, true, 1.0, json::array(), 0.0}, {0x85, 0xF8, 0xFA, 0xF9, 0xFD, 0x80, 0xFC, 0xFE});
|
||||
check_bon8({{{1}}}, {0x81, 0x81, 0x81, 0x91});
|
||||
check_bon8({{{"foo"}}}, {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF});
|
||||
check_bon8({{{""}}}, {0x81, 0x81, 0x81, 0xFF});
|
||||
|
||||
SECTION("large array")
|
||||
{
|
||||
json j = json::array();
|
||||
bytes expected = {0x85};
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
j.push_back(nullptr);
|
||||
expected.push_back(0xFA);
|
||||
}
|
||||
expected.push_back(0xFE);
|
||||
check_bon8(j, expected);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("objects")
|
||||
{
|
||||
check_bon8(json::object(), {0x86});
|
||||
check_bon8({{"foo", nullptr}}, {0x87, 'f', 'o', 'o', 0xFA});
|
||||
check_bon8({{"", true}, {"foo", nullptr}}, {0x88, 0xFF, 0xF9, 'f', 'o', 'o', 0xFA});
|
||||
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}}, {0x89, 'a', 0x91, 'b', 0x92, 'c', 0x93});
|
||||
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}, {0x8A, 'a', 0x91, 'b', 0x92, 'c', 0x93, 'd', 0x94});
|
||||
const json five = {{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}, {"five", 5}};
|
||||
check_bon8(five, {0x8B, 'f', 'i', 'v', 'e', 0x95, 'f', 'o', 'u', 'r', 0x94, 'o', 'n', 'e', 0x91, 't', 'h', 'r', 'e', 'e', 0x93, 't', 'w', 'o', 0x92, 0xFE});
|
||||
}
|
||||
|
||||
SECTION("binary values are written as arrays of integers")
|
||||
{
|
||||
CHECK(json::to_bon8(json::binary({})) == bytes{0x80});
|
||||
CHECK(json::to_bon8(json::binary({0x00, 0x27, 0x28, 0xFF})) == bytes{0x84, 0x90, 0xB7, 0xC2, 0x00, 0xC3, 0x57});
|
||||
CHECK(json::to_bon8(json::binary({1, 2, 3, 4, 5}, 42)) == bytes{0x85, 0x91, 0x92, 0x93, 0x94, 0x95, 0xFE});
|
||||
CHECK(json::to_bon8({"a", json::binary({1})}) == bytes{0x82, 'a', 0x81, 0x91});
|
||||
CHECK(json::from_bon8(json::to_bon8(json::binary({1, 2, 3, 4, 5}))) == json({1, 2, 3, 4, 5}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("examples from the specification")
|
||||
{
|
||||
check_bon8("ab", {'a', 'b', 0xFF});
|
||||
check_bon8({"ab", "bc"}, {0x82, 'a', 'b', 0xFF, 'b', 'c', 0xFF});
|
||||
check_bon8({"a", "b", "c", "d", "e"}, {0x85, 'a', 0xFF, 'b', 0xFF, 'c', 0xFF, 'd', 0xFF, 'e', 0xFE});
|
||||
check_bon8({{"ab", 1}, {"bc", 2}}, {0x88, 'a', 'b', 0x91, 'b', 'c', 0x92});
|
||||
// the specification prints this example without the end-of-string
|
||||
// markers after "b" and "c", which its own rules require: without
|
||||
// them, the bytes read as the single string "bcd"
|
||||
check_bon8({{"a", {"b", "c"}}, {"d", 1}}, {0x88, 'a', 0x82, 'b', 0xFF, 'c', 0xFF, 'd', 0x91});
|
||||
check_bon8({{"", 1}, {"a", 2}}, {0x88, 0xFF, 0x91, 'a', 0x92});
|
||||
}
|
||||
|
||||
SECTION("examples from the discussion of the specification (#2980)")
|
||||
{
|
||||
check_bon8({{"a", ""}, {"c", "d"}}, {0x88, 'a', 0xFF, 0xFF, 'c', 0xFF, 'd', 0xFF});
|
||||
}
|
||||
|
||||
SECTION("end of strings")
|
||||
{
|
||||
SECTION("a string ends at the first byte of an integer")
|
||||
{
|
||||
check_bon8({{"a", 100}}, {0x87, 'a', 0xC2, 0x3C});
|
||||
check_bon8({{"a", -9}}, {0x87, 'a', 0xC0});
|
||||
check_bon8({{"a", -10}}, {0x87, 'a', 0xC1});
|
||||
check_bon8({{"a", 5}}, {0x87, 'a', 0x95});
|
||||
check_bon8({{"a", 100000}}, {0x87, 'a', 0xE2, 0x77, 0x78});
|
||||
check_bon8({{"a", -1000000}}, {0x87, 'a', 0xF0, 0xCB, 0x3A, 0xB5});
|
||||
check_bon8({{"a", 67637032}}, {0x87, 'a', 0x8C, 0x04, 0x08, 0x0F, 0x28});
|
||||
check_bon8({{"a", 2147483648}}, {0x87, 'a', 0x8D, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8({"\xC2\xA3", 100}, {0x82, 0xC2, 0xA3, 0xC2, 0x3C});
|
||||
check_bon8({"\xF0\x9F\x80\x84", -1000000}, {0x82, 0xF0, 0x9F, 0x80, 0x84, 0xF0, 0xCB, 0x3A, 0xB5});
|
||||
}
|
||||
|
||||
SECTION("a string ends at the first byte of other values")
|
||||
{
|
||||
check_bon8({"a", 2.0}, {0x82, 'a', 0x8E, 0x40, 0x00, 0x00, 0x00});
|
||||
check_bon8({"a", 0.1}, {0x82, 'a', 0x8F, 0x3F, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A});
|
||||
check_bon8({"a", nullptr, "b", true, "c", false}, {0x85, 'a', 0xFA, 'b', 0xF9, 'c', 0xF8, 0xFE});
|
||||
check_bon8({"a", -1.0, "b", 0.0, "c", 1.0}, {0x85, 'a', 0xFB, 'b', 0xFC, 'c', 0xFD, 0xFE});
|
||||
check_bon8({"a", json::array()}, {0x82, 'a', 0x80});
|
||||
check_bon8({"a", json::object()}, {0x82, 'a', 0x86});
|
||||
check_bon8({{"a", {1, 2, 3, 4, 5}}}, {0x87, 'a', 0x85, 0x91, 0x92, 0x93, 0x94, 0x95, 0xFE});
|
||||
check_bon8({{"a", {{"b", 1}, {"c", 2}, {"d", 3}, {"e", 4}, {"f", 5}}}}, {0x87, 'a', 0x8B, 'b', 0x91, 'c', 0x92, 'd', 0x93, 'e', 0x94, 'f', 0x95, 0xFE});
|
||||
}
|
||||
|
||||
SECTION("a string ends at the end of its container")
|
||||
{
|
||||
check_bon8({1, 2, 3, 4, "e"}, {0x85, 0x91, 0x92, 0x93, 0x94, 'e', 0xFE});
|
||||
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", "f"}}, {0x8B, 'a', 0x91, 'b', 0x92, 'c', 0x93, 'd', 0x94, 'e', 0xFF, 'f', 0xFE});
|
||||
check_bon8({{1, 2, 3, 4, "e"}, 1}, {0x82, 0x85, 0x91, 0x92, 0x93, 0x94, 'e', 0xFE, 0x91});
|
||||
check_bon8({{1, 2, 3, 4, "\xC2\xA3"}, 100}, {0x82, 0x85, 0x91, 0x92, 0x93, 0x94, 0xC2, 0xA3, 0xFE, 0xC2, 0x3C});
|
||||
}
|
||||
|
||||
SECTION("a string that another string follows is terminated")
|
||||
{
|
||||
check_bon8({"s", "s"}, {0x82, 's', 0xFF, 's', 0xFF});
|
||||
check_bon8({"", "s"}, {0x82, 0xFF, 's', 0xFF});
|
||||
check_bon8({"s", ""}, {0x82, 's', 0xFF, 0xFF});
|
||||
check_bon8({"", ""}, {0x82, 0xFF, 0xFF});
|
||||
check_bon8({{"a", "b"}, {"c", "d"}}, {0x88, 'a', 0xFF, 'b', 0xFF, 'c', 0xFF, 'd', 0xFF});
|
||||
check_bon8({{"a", ""}}, {0x87, 'a', 0xFF, 0xFF});
|
||||
check_bon8({{"", ""}}, {0x87, 0xFF, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("a container that ends with a string and that a string follows")
|
||||
{
|
||||
check_bon8({{"a"}, "b"}, {0x82, 0x81, 'a', 0xFF, 'b', 0xFF});
|
||||
check_bon8({{{{"a"}}}, "b"}, {0x82, 0x81, 0x81, 0x81, 'a', 0xFF, 'b', 0xFF});
|
||||
check_bon8({{"a", {"x"}}, {"b", 1}}, {0x88, 'a', 0x81, 'x', 0xFF, 'b', 0x91});
|
||||
check_bon8({{"a", {{"b", "c"}}}, {"d", 1}}, {0x88, 'a', 0x87, 'b', 0xFF, 'c', 0xFF, 'd', 0x91});
|
||||
check_bon8({{"a"}, {"b"}}, {0x82, 0x81, 'a', 0x81, 'b', 0xFF});
|
||||
}
|
||||
|
||||
SECTION("a string that ends the message is terminated")
|
||||
{
|
||||
check_bon8({"a"}, {0x81, 'a', 0xFF});
|
||||
check_bon8({{"a", "b"}}, {0x87, 'a', 0xFF, 'b', 0xFF});
|
||||
check_bon8({{{"a"}}}, {0x81, 0x81, 0x81, 'a', 0xFF});
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("non-canonical input is accepted")
|
||||
{
|
||||
// an integer with a longer encoding than necessary
|
||||
CHECK(json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00, 0x01}) == 1);
|
||||
CHECK(json::from_bon8(bytes{0x8D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) == -1);
|
||||
// a float with a longer encoding than necessary
|
||||
CHECK(json::from_bon8(bytes{0x8F, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) == 1.0);
|
||||
CHECK(json::from_bon8(bytes{0x8E, 0x00, 0x00, 0x00, 0x00}) == 0.0);
|
||||
// an unsized container with few elements
|
||||
CHECK(json::from_bon8(bytes{0x85, 0x91, 0x92, 0xFE}) == json({1, 2}));
|
||||
CHECK(json::from_bon8(bytes{0x85, 0xFE}) == json::array());
|
||||
CHECK(json::from_bon8(bytes{0x8B, 'a', 0x91, 0xFE}) == json({{"a", 1}}));
|
||||
// unsorted keys
|
||||
CHECK(json::from_bon8(bytes{0x88, 'b', 0x91, 'a', 0x92}) == json({{"a", 2}, {"b", 1}}));
|
||||
// an end-of-string marker that is not needed
|
||||
CHECK(json::from_bon8(bytes{0x82, 'a', 0xFF, 0x91}) == json({"a", 1}));
|
||||
CHECK(json::from_bon8(bytes{0x87, 'a', 0xFF, 0x91}) == json({{"a", 1}}));
|
||||
}
|
||||
|
||||
SECTION("types of values read")
|
||||
{
|
||||
CHECK(json::from_bon8(bytes{0x90}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0xC2, 0x00}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00, 0x00}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0xB8}).is_number_integer());
|
||||
CHECK(!json::from_bon8(bytes{0xB8}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0xC2, 0xC0}).is_number_integer());
|
||||
CHECK(json::from_bon8(bytes{0xFC}).is_number_float());
|
||||
CHECK(json::from_bon8(bytes{0x8E, 0x40, 0x00, 0x00, 0x00}).is_number_float());
|
||||
CHECK(json::from_bon8(bytes{0xFF}).is_string());
|
||||
}
|
||||
|
||||
SECTION("errors")
|
||||
{
|
||||
json _;
|
||||
|
||||
SECTION("empty input")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes()), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bon8(bytes(), true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("too short numbers")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8C}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8E, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8F, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xC2}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE0, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x82, 'a', 0xF0, 0xC0}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("unterminated strings")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{'a'}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 'b'}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x9F, 0x80, 0x84}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x9F, 0x80}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8")
|
||||
{
|
||||
// overlong
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE0, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x80, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
||||
// surrogate
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xED, 0xA0, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0xA0", json::parse_error&);
|
||||
// above U+10FFFF
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF4, 0x90, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x90", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF5, 0x80, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
||||
// missing continuation byte
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE2, 0x82, 'a', 0xFF}), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x61", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xE2, 0x82, 'b'}), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x62", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("end-of-container marker where a value is expected")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xFE}), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 0xFE}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8B, 'a', 0xFE}), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("keys that are not strings")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0x91, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0x91", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xFA, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xFA", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0x80, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0x80", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xFE}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xFE", json::parse_error&);
|
||||
// an integer that begins with a UTF-8 lead byte
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xC2, 0x00, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xC2", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x88, 'a', 0x91, 0xF0, 0xC0, 0x00, 0x00, 0x91}), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BON8 key: expected a string; last byte: 0xF0", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("unterminated containers")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x82, 0x91}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x85, 0x91}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8B, 'a', 0x91}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a', 0xFF}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("strict mode")
|
||||
{
|
||||
const bytes vec = {0x90, 0x90};
|
||||
CHECK(json::from_bon8(vec, false) == 0);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&);
|
||||
CHECK(json::from_bon8(vec, true, false).is_discarded());
|
||||
|
||||
// the byte after a string that ends a container
|
||||
const bytes vec2 = {0x81, 'a', 0x90};
|
||||
CHECK(json::from_bon8(vec2, false) == json({"a"}));
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec2), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&);
|
||||
|
||||
const bytes vec3 = {0x81, 'a', 0xC2, 0x00};
|
||||
CHECK(json::from_bon8(vec3, false) == json({"a"}));
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec3), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0xC2", json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("SAX aborts")
|
||||
{
|
||||
SECTION("start_array(len)")
|
||||
{
|
||||
const bytes v = {0x83, 0x91, 0x92, 0x93};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("start_array()")
|
||||
{
|
||||
const bytes v = {0x85, 0x91, 0xFE};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("end_array()")
|
||||
{
|
||||
const bytes v = {0x85, 0x91, 0xFE};
|
||||
SaxCountdown scp(2);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("start_object(len)")
|
||||
{
|
||||
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("key()")
|
||||
{
|
||||
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
||||
SaxCountdown scp(1);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("end_object()")
|
||||
{
|
||||
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
||||
SaxCountdown scp(3);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("values")
|
||||
{
|
||||
const std::vector<bytes> values =
|
||||
{
|
||||
{0xFA}, {0xF9}, {0x91}, {0xB8}, {0xC2, 0x00}, {0xC2, 0xC0}, {0xE0, 0x00, 0x00},
|
||||
{0x8C, 0, 0, 0, 1}, {0x8D, 0, 0, 0, 0, 0, 0, 0, 1}, {0xFB}, {0xFC}, {0xFD},
|
||||
{0x8E, 0x40, 0, 0, 0}, {0x8F, 0x40, 0, 0, 0, 0, 0, 0, 0}, {'a', 0xFF}, {0xFF}
|
||||
};
|
||||
for (const auto& v : values)
|
||||
{
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use this testcase outside [hide] to run it with Valgrind
|
||||
TEST_CASE("BON8 nesting does not consume the call stack")
|
||||
{
|
||||
// Note that deeply nested values must not be compared, copied or dumped
|
||||
// here: those operations are still recursive. Depth is measured by
|
||||
// descending instead.
|
||||
|
||||
SECTION("an unterminated chain is reported, not crashed on")
|
||||
{
|
||||
json _;
|
||||
const bytes input(300000, 0x81);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(input), "[json.exception.parse_error.110] parse error at byte 300001: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bon8(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read through the SAX interface")
|
||||
{
|
||||
bytes input(300000, 0x85);
|
||||
input.push_back(0x91); // innermost value
|
||||
input.insert(input.end(), 300000, 0xFE);
|
||||
|
||||
SaxCountdown accept_all(600001);
|
||||
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read into a value")
|
||||
{
|
||||
const std::size_t depth = 10000;
|
||||
bytes input;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
input.push_back(0x87);
|
||||
input.push_back('a');
|
||||
}
|
||||
// the innermost key is followed by a string, so it is terminated
|
||||
input.push_back(0xFF);
|
||||
input.push_back('b');
|
||||
input.push_back(0xFF);
|
||||
|
||||
json j = json::from_bon8(input);
|
||||
|
||||
std::size_t measured = 0;
|
||||
const json* p = &j;
|
||||
while (p->is_object() && !p->empty())
|
||||
{
|
||||
p = &p->at("a");
|
||||
++measured;
|
||||
}
|
||||
CHECK(measured == depth);
|
||||
CHECK(*p == "b");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("single BON8 roundtrip")
|
||||
{
|
||||
SECTION("sample.json")
|
||||
{
|
||||
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
|
||||
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BON8 file
|
||||
auto packed = utils::read_binary_file(filename + ".bon8");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
|
||||
SECTION("roundtrips")
|
||||
{
|
||||
SECTION("std::ostringstream")
|
||||
{
|
||||
std::basic_ostringstream<char> ss;
|
||||
json::to_bon8(j1, ss);
|
||||
json j3 = json::from_bon8(ss.str());
|
||||
CHECK(j1 == j3);
|
||||
}
|
||||
|
||||
SECTION("std::string")
|
||||
{
|
||||
std::string s;
|
||||
json::to_bon8(j1, s);
|
||||
json j3 = json::from_bon8(s);
|
||||
CHECK(j1 == j3);
|
||||
}
|
||||
}
|
||||
|
||||
// check with different start index
|
||||
packed.insert(packed.begin(), 5, 0xff);
|
||||
CHECK(j1 == json::from_bon8(packed.begin() + 5, packed.end()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Parse BON8 directly from a file using iterator and sentinel")
|
||||
{
|
||||
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.bon8";
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
const std::istreambuf_iterator<char> first(file);
|
||||
const json parsed = json::from_bon8(first, utils::istreambuf_sentinel{});
|
||||
CHECK((parsed.is_object() || parsed.is_array()));
|
||||
}
|
||||
|
||||
TEST_CASE("BON8 roundtrips" * doctest::skip())
|
||||
{
|
||||
SECTION("input from HikoGUI")
|
||||
{
|
||||
// The .bon8 files were created with the BON8 encoder of HikoGUI
|
||||
// (https://github.com/hikoworks/hikogui), the reference implementation
|
||||
// of the format. Both implementations sort object keys, so the output
|
||||
// is compared byte for byte.
|
||||
for (std::string filename :
|
||||
{
|
||||
TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/1.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/2.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/3.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/4.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/5.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
|
||||
TEST_DATA_DIRECTORY "/json_testsuite/sample.json",
|
||||
TEST_DATA_DIRECTORY "/json_tests/pass1.json",
|
||||
TEST_DATA_DIRECTORY "/json_tests/pass2.json",
|
||||
TEST_DATA_DIRECTORY "/json_tests/pass3.json",
|
||||
TEST_DATA_DIRECTORY "/regression/floats.json",
|
||||
TEST_DATA_DIRECTORY "/regression/signed_ints.json",
|
||||
TEST_DATA_DIRECTORY "/regression/working_file.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty-string.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_false.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_null.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e+1.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e1.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_after_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_one.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_real.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty_key.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_comments.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_null_escape.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_pi.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_uEscape.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json"
|
||||
})
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BON8 file
|
||||
const auto packed = utils::read_binary_file(filename + ".bon8");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(packed));
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
std::ifstream f_bon8(filename + ".bon8", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(f_bon8));
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": iterator pair");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(packed.begin(), packed.end()));
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
||||
std::vector<uint8_t> vec;
|
||||
json::to_bon8(j1, vec);
|
||||
CHECK(vec == packed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
TEST_CASE("BON8 with std::byte")
|
||||
{
|
||||
SECTION("vector roundtrip")
|
||||
{
|
||||
const json original =
|
||||
{
|
||||
{"name", "test"},
|
||||
{"value", 42},
|
||||
{"array", {1, 2, 3}}
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||
std::vector<std::byte> bon8_data(temp.size());
|
||||
for (size_t i = 0; i < temp.size(); ++i)
|
||||
{
|
||||
bon8_data[i] = std::byte(temp[i]);
|
||||
}
|
||||
|
||||
json from_bytes;
|
||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
||||
CHECK(from_bytes == original);
|
||||
}
|
||||
|
||||
SECTION("empty vector")
|
||||
{
|
||||
const std::vector<std::byte> empty_data;
|
||||
CHECK_THROWS_WITH_AS([&]()
|
||||
{
|
||||
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
||||
return true;
|
||||
}
|
||||
(),
|
||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||
json::parse_error&);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,167 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// This file tests the opt-in JSON_BRACE_INIT_COPY_SEMANTICS, so it defines the
|
||||
// macro itself rather than relying on a -D flag, and runs in every build.
|
||||
#ifdef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define STRINGIZE_EX(x) #x
|
||||
#define STRINGIZE(x) STRINGIZE_EX(x)
|
||||
|
||||
TEST_CASE("JSON_BRACE_INIT_COPY_SEMANTICS")
|
||||
{
|
||||
SECTION("the macro is part of the ABI tag")
|
||||
{
|
||||
const std::string ns = STRINGIZE(NLOHMANN_JSON_NAMESPACE);
|
||||
// other tags may come before it, e.g. json_abi_ldvcmp_bics
|
||||
CHECK(ns.find("_bics") != std::string::npos);
|
||||
}
|
||||
|
||||
SECTION("single-element brace initialization copies the element (#5074)")
|
||||
{
|
||||
json const j_obj = {{"key", "value"}, {"num", 42}};
|
||||
json const j_arr = {1, 2, 3};
|
||||
|
||||
// object: brace init copies instead of wrapping
|
||||
json const j1{j_obj};
|
||||
CHECK(j1.is_object());
|
||||
CHECK(j1 == j_obj);
|
||||
|
||||
// array: brace init copies instead of wrapping
|
||||
json const j2{j_arr};
|
||||
CHECK(j2.is_array());
|
||||
CHECK(j2.size() == 3);
|
||||
CHECK(j2 == j_arr);
|
||||
|
||||
// this applies to any single element, not only to JSON values
|
||||
json const j3{true};
|
||||
CHECK(j3.is_boolean());
|
||||
|
||||
json const j4{42};
|
||||
CHECK(j4.is_number_integer());
|
||||
|
||||
json const j5 = {1};
|
||||
CHECK(j5 == 1);
|
||||
|
||||
json const j6 = {"text"};
|
||||
CHECK(j6 == "text");
|
||||
|
||||
json const j7 = {{1, 2}};
|
||||
CHECK(j7 == json::array({1, 2}));
|
||||
}
|
||||
|
||||
SECTION("what the macro does not change")
|
||||
{
|
||||
// lists with more than one element are unaffected
|
||||
json const j1 = {1, 2};
|
||||
CHECK(j1.is_array());
|
||||
CHECK(j1.size() == 2);
|
||||
|
||||
// a single [string, value] pair still describes an object
|
||||
json const j2 = {{"key", "value"}};
|
||||
CHECK(j2.is_object());
|
||||
CHECK(j2["key"] == "value");
|
||||
|
||||
// json::array() always creates an array
|
||||
json const j3 = json::array({1});
|
||||
CHECK(j3.is_array());
|
||||
CHECK(j3.size() == 1);
|
||||
CHECK(j3[0] == 1);
|
||||
|
||||
json const j_obj = {{"key", "value"}};
|
||||
json const j4 = json::array({j_obj});
|
||||
CHECK(j4.is_array());
|
||||
CHECK(j4.size() == 1);
|
||||
CHECK(j4[0] == j_obj);
|
||||
}
|
||||
|
||||
SECTION("conversions build the same values as without the macro")
|
||||
{
|
||||
SECTION("one-element std::tuple")
|
||||
{
|
||||
json const j1 = std::tuple<int> {5};
|
||||
CHECK(j1.dump() == "[5]");
|
||||
CHECK(std::get<0>(j1.get<std::tuple<int>>()) == 5);
|
||||
|
||||
json const j2 = std::tuple<std::string> {"text"};
|
||||
CHECK(j2.dump() == "[\"text\"]");
|
||||
CHECK(std::get<0>(j2.get<std::tuple<std::string>>()) == "text");
|
||||
|
||||
json const j3 = std::tuple<json> {json::array({1, 2})};
|
||||
CHECK(j3.dump() == "[[1,2]]");
|
||||
|
||||
// as without the macro, a [string, value] pair becomes an object
|
||||
// member (see the known limitation documented for std::pair)
|
||||
json const j4 = std::tuple<std::pair<std::string, int>> {{"a", 1}};
|
||||
CHECK(j4.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("tuples with more elements")
|
||||
{
|
||||
json const j1 = std::tuple<int, std::string> {1, "a"};
|
||||
CHECK(j1.dump() == "[1,\"a\"]");
|
||||
|
||||
json const j2 = std::tuple<> {};
|
||||
CHECK(j2.dump() == "[]");
|
||||
}
|
||||
|
||||
SECTION("one-element containers")
|
||||
{
|
||||
json const j1 = std::vector<int> {1};
|
||||
CHECK(j1.dump() == "[1]");
|
||||
CHECK(j1.get<std::vector<int>>() == std::vector<int> {1});
|
||||
|
||||
std::array<int, 1> const arr = {{1}};
|
||||
json const j2 = arr;
|
||||
CHECK(j2.dump() == "[1]");
|
||||
|
||||
json const j3 = std::list<std::string> {"a"};
|
||||
CHECK(j3.dump() == "[\"a\"]");
|
||||
|
||||
json const j4 = std::map<std::string, int> {{"a", 1}};
|
||||
CHECK(j4.dump() == "{\"a\":1}");
|
||||
|
||||
json const j5 = std::map<int, int> {{1, 2}};
|
||||
CHECK(j5.dump() == "[[1,2]]");
|
||||
}
|
||||
|
||||
SECTION("std::pair")
|
||||
{
|
||||
json const j = std::pair<int, int> {1, 2};
|
||||
CHECK(j.dump() == "[1,2]");
|
||||
CHECK((j.get<std::pair<int, int>>() == std::pair<int, int> {1, 2}));
|
||||
}
|
||||
|
||||
SECTION("items()")
|
||||
{
|
||||
json j_obj = {{"key", 1}};
|
||||
for (const auto& el : j_obj.items())
|
||||
{
|
||||
json const j = el;
|
||||
CHECK(j.dump() == "{\"key\":1}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-26
@@ -199,32 +199,6 @@ TEST_CASE("BSON")
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 10: syntax error while parsing BSON string: string length must be at least 1, is -2147483648", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in string (see #5529)")
|
||||
{
|
||||
// a BSON document with a string field "k" whose value 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
|
||||
std::vector<std::uint8_t> const v =
|
||||
{
|
||||
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x02, /// entry: string (UTF-8)
|
||||
'k', 0x00, // key "k"
|
||||
0x03, 0x00, 0x00, 0x00, // string length (including trailing zero byte)
|
||||
0xc0, 0xae, // ill-formed UTF-8
|
||||
0x00, // string terminator
|
||||
0x00 // end marker
|
||||
};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing BSON string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||
|
||||
// valid UTF-8 must still round-trip
|
||||
const json j = {{"k", "h\xc3\xa9llo, w\xc3\xb6rld! \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"}}; // héllo, wörld! 日本語
|
||||
CHECK(json::from_bson(json::to_bson(j)) == j);
|
||||
}
|
||||
|
||||
SECTION("objects")
|
||||
{
|
||||
SECTION("empty object")
|
||||
@@ -817,6 +791,15 @@ TEST_CASE("BSON")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("regression test - BSON binary subtype rejects a value that doesn't fit a single byte")
|
||||
{
|
||||
json const doc255 = {{"b", json::binary({1, 2}, 255)}};
|
||||
CHECK(json::from_bson(json::to_bson(doc255))["b"].get_binary().subtype() == 255);
|
||||
|
||||
CHECK_THROWS_AS(json::to_bson(json{{"b", json::binary({1, 2}, 256)}}), json::out_of_range);
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json{{"b", json::binary({1, 2}, 300)}}), "[json.exception.out_of_range.415] subtype 300 is too large for the BSON binary subtype (max 255)", json::out_of_range);
|
||||
}
|
||||
|
||||
TEST_CASE("BSON input/output_adapters")
|
||||
{
|
||||
const json json_representation =
|
||||
|
||||
@@ -42,6 +42,39 @@ TEST_CASE("byte_container_with_subtype")
|
||||
CHECK(container.subtype() == static_cast<subtype_type>(-1));
|
||||
}
|
||||
|
||||
SECTION("move semantics")
|
||||
{
|
||||
// the rvalue-reference constructor (without a subtype) must actually move
|
||||
// the passed-in container rather than copy it; comparing the buffer address
|
||||
// before and after is a stronger check than just observing the source is
|
||||
// empty afterward, since a copy-then-clear could also leave it empty
|
||||
{
|
||||
std::vector<std::uint8_t> bytes = {{0xCA, 0xFE, 0xBA, 0xBE}};
|
||||
const auto* const data_ptr = bytes.data();
|
||||
|
||||
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container(std::move(bytes));
|
||||
|
||||
CHECK(container.size() == 4);
|
||||
CHECK(container.data() == data_ptr);
|
||||
CHECK(!container.has_subtype());
|
||||
CHECK(bytes.empty()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move,hicpp-invalid-access-moved)
|
||||
}
|
||||
|
||||
// same check for the rvalue-reference constructor that also takes a subtype
|
||||
{
|
||||
std::vector<std::uint8_t> bytes = {{0xCA, 0xFE, 0xBA, 0xBE}};
|
||||
const auto* const data_ptr = bytes.data();
|
||||
|
||||
nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>> container(std::move(bytes), 42);
|
||||
|
||||
CHECK(container.size() == 4);
|
||||
CHECK(container.data() == data_ptr);
|
||||
CHECK(container.has_subtype());
|
||||
CHECK(container.subtype() == 42);
|
||||
CHECK(bytes.empty()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move,hicpp-invalid-access-moved)
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("comparisons")
|
||||
{
|
||||
std::vector<std::uint8_t> const bytes = {{0xCA, 0xFE, 0xBA, 0xBE}};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user