mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 14:40:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37316992ed | ||
|
|
5ecb704f6b | ||
|
|
0490778fc3 | ||
|
|
0a365865f9 | ||
|
|
5379e04ce4 |
@@ -17,11 +17,11 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
macos-14:
|
||||
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
@@ -36,11 +36,11 @@ jobs:
|
||||
- name: Test
|
||||
run: cd build ; ctest -j 10 --output-on-failure
|
||||
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
macos-26:
|
||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
||||
xcode: ['26.4.1', '26.5', '26.6']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
|
||||
@@ -31,48 +31,6 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_gcc
|
||||
|
||||
ci_meson_install:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
- name: Check that Meson and pkg-config offer the CMake options
|
||||
run: make check_build_options
|
||||
- name: Install Meson
|
||||
run: pip install meson
|
||||
- name: Install with Meson
|
||||
run: |
|
||||
meson setup build-meson --prefix=${{ github.workspace }}/install
|
||||
meson install -C build-meson
|
||||
- name: Use the installed package with find_package
|
||||
run: |
|
||||
cmake -S tests/cmake_import/project -B build-import -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install
|
||||
cmake --build build-import
|
||||
- name: Install with Meson and non-default options
|
||||
run: |
|
||||
meson setup build-meson-options --prefix=${{ github.workspace }}/install-options -DMultipleHeaders=true -DDiagnostics=true -DGlobalUDLs=false -DDisableTupleReferenceConversion=true
|
||||
meson install -C build-meson-options
|
||||
- name: Check that the options reach the installed files
|
||||
run: |
|
||||
test -d install-options/include/nlohmann/detail
|
||||
cflags=$(PKG_CONFIG_PATH=${{ github.workspace }}/install-options/share/pkgconfig pkg-config --cflags nlohmann_json)
|
||||
echo "$cflags"
|
||||
echo "$cflags" | grep -q -- '-DJSON_DIAGNOSTICS=1'
|
||||
echo "$cflags" | grep -q -- '-DJSON_USE_GLOBAL_UDLS=0'
|
||||
echo "$cflags" | grep -q -- '-DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1'
|
||||
grep -q 'JSON_USE_GLOBAL_UDLS=0;JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1;JSON_DIAGNOSTICS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake
|
||||
cmake -S tests/cmake_import/project -B build-import-options -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-options
|
||||
cmake --build build-import-options
|
||||
- name: Install with Meson and the include directory outside the prefix
|
||||
run: |
|
||||
meson setup build-meson-split --prefix=${{ github.workspace }}/install-split --includedir=${{ github.workspace }}/install-split-dev/include
|
||||
meson install -C build-meson-split
|
||||
cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split
|
||||
cmake --build build-import-split
|
||||
|
||||
ci_infer:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -251,7 +209,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||
container: gcc:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
+1
-27
@@ -186,33 +186,7 @@ if (MSVC)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Install a pkg-config file, so other tools can find this. It carries the same
|
||||
# compile definitions as the target above.
|
||||
set(NLOHMANN_JSON_PKGCONFIG_CFLAGS "")
|
||||
if (NOT JSON_GlobalUDLs)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_GLOBAL_UDLS=0")
|
||||
endif()
|
||||
if (NOT JSON_ImplicitConversions)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_IMPLICIT_CONVERSIONS=0")
|
||||
endif()
|
||||
if (JSON_DisableEnumSerialization)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_ENUM_SERIALIZATION=1")
|
||||
endif()
|
||||
if (JSON_DisableTupleReferenceConversion)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1")
|
||||
endif()
|
||||
if (JSON_Diagnostics)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTICS=1")
|
||||
endif()
|
||||
if (JSON_Diagnostic_Positions)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTIC_POSITIONS=1")
|
||||
endif()
|
||||
if (JSON_LegacyDiscardedValueComparison)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1")
|
||||
endif()
|
||||
if (JSON_StrictNulHandling)
|
||||
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_NUL_HANDLING=1")
|
||||
endif()
|
||||
# Install a pkg-config file, so other tools can find this.
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
|
||||
|
||||
@@ -249,31 +249,9 @@ make BUILD.bazel
|
||||
|
||||
The "Check amalgamation" workflow fails if the file is out of date.
|
||||
|
||||
### `meson.build` and `meson_options.txt`
|
||||
### `meson.build`
|
||||
|
||||
Meson build definitions suitable for use as a subproject ("wrap" in Meson terminology).
|
||||
|
||||
Projects wishing to use the wrap can execute:
|
||||
```sh
|
||||
meson wrap install nlohmann_json
|
||||
```
|
||||
|
||||
Which allows Meson to build from source when a system provided dependency isn't available.
|
||||
|
||||
To build directly:
|
||||
```sh
|
||||
meson setup builddir
|
||||
ninja -C builddir
|
||||
```
|
||||
|
||||
`meson_options.txt` defines the options, which mirror the CMake options that change the library's target (for example,
|
||||
`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. `make check_build_options`
|
||||
([`tools/check_build_options`](tools/check_build_options/README.md)) checks in CI that both files and the pkg-config files
|
||||
stay in sync with the CMake options.
|
||||
|
||||
When installing, `meson.build` installs the headers, a pkg-config file, and the CMake package config files, so that
|
||||
`find_package(nlohmann_json)` works. As Meson cannot generate `nlohmann_jsonTargets.cmake` itself, it is created from
|
||||
the template `cmake/nlohmann_jsonTargets.cmake.in`, which is only used by Meson.
|
||||
The build definition for the [Meson](https://mesonbuild.com) build system.
|
||||
|
||||
### `Package.swift`
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check check_build_options
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check
|
||||
|
||||
##########################################################################
|
||||
# configuration
|
||||
@@ -124,10 +124,6 @@ macro_builder_check:
|
||||
diff "$$TMPDIR/paste.hpp" "$$TMPDIR/paste_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_EXPAND..NLOHMANN_JSON_DOUBLE_PASTE63) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1); \
|
||||
diff "$$TMPDIR/type_body.hpp" "$$TMPDIR/type_body_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_TYPE_BODY) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1)
|
||||
|
||||
# check that the Meson build and the pkg-config files offer the options of the CMake target
|
||||
check_build_options:
|
||||
python3 tools/check_build_options/check_build_options.py .
|
||||
|
||||
# check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources
|
||||
check-amalgamation:
|
||||
@mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~
|
||||
@@ -185,7 +181,7 @@ json.tar.xz:
|
||||
# We use `-X` to make the resulting ZIP file reproducible, see
|
||||
# <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>.
|
||||
include.zip: BUILD.bazel
|
||||
zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build meson_options.txt LICENSE.MIT
|
||||
zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT
|
||||
|
||||
# Create the files for a release and add signatures and hashes.
|
||||
release: include.zip json.tar.xz
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# Imported target for installations made with Meson (see meson.build).
|
||||
#
|
||||
# CMake installations generate this file with install(EXPORT ...). Meson cannot
|
||||
# do that, but as the library is header-only, the target only needs an include
|
||||
# directory, the C++ standard, and the compile definitions of the options that
|
||||
# differ from their defaults. Paths are computed relative to this file so that
|
||||
# the installation can be relocated (e.g., into a sysroot), unless includedir or
|
||||
# datadir is outside the prefix.
|
||||
|
||||
if(TARGET @PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@)
|
||||
return()
|
||||
endif()
|
||||
|
||||
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_CONFIG_TO_PREFIX@" ABSOLUTE)
|
||||
# As in CMake's generated file: avoid "//include" for an installation to "/".
|
||||
if(_IMPORT_PREFIX STREQUAL "/")
|
||||
set(_IMPORT_PREFIX "")
|
||||
endif()
|
||||
|
||||
add_library(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ INTERFACE IMPORTED)
|
||||
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "@NLOHMANN_JSON_INCLUDE_DIR@"
|
||||
)
|
||||
if(CMAKE_VERSION VERSION_LESS 3.8)
|
||||
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
|
||||
INTERFACE_COMPILE_FEATURES cxx_range_for
|
||||
)
|
||||
else()
|
||||
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
|
||||
INTERFACE_COMPILE_FEATURES cxx_std_11
|
||||
)
|
||||
endif()
|
||||
|
||||
set(_NLOHMANN_JSON_COMPILE_DEFINITIONS "@NLOHMANN_JSON_COMPILE_DEFINITIONS@")
|
||||
if(_NLOHMANN_JSON_COMPILE_DEFINITIONS)
|
||||
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "${_NLOHMANN_JSON_COMPILE_DEFINITIONS}"
|
||||
)
|
||||
endif()
|
||||
|
||||
unset(_NLOHMANN_JSON_COMPILE_DEFINITIONS)
|
||||
unset(_IMPORT_PREFIX)
|
||||
@@ -4,4 +4,4 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
|
||||
Name: @PROJECT_NAME@
|
||||
Description: JSON for Modern C++
|
||||
Version: @PROJECT_VERSION@
|
||||
Cflags: -I${includedir}@NLOHMANN_JSON_PKGCONFIG_CFLAGS@
|
||||
Cflags: -I${includedir}
|
||||
|
||||
@@ -68,6 +68,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to BJData"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -119,4 +121,6 @@ Linear in the size of the JSON value `j`.
|
||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
@@ -58,6 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded
|
||||
(the top-level value itself is covered by `type_error.317` above, since it must be an object); example:
|
||||
`"cannot serialize discarded value to BSON"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -110,6 +113,8 @@ pass before anything is written.
|
||||
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
||||
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
||||
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
||||
- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently
|
||||
skipped, producing a document whose declared size did not match what was actually written.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||
|
||||
@@ -49,6 +49,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to CBOR"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -86,3 +88,5 @@ Linear in the size of the JSON value `j`.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid CBOR.
|
||||
|
||||
@@ -54,6 +54,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict`
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to MessagePack"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -108,3 +110,5 @@ Linear in the size of the JSON value `j`.
|
||||
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
||||
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||
`number_unsigned_t`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid MessagePack.
|
||||
|
||||
@@ -61,6 +61,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to UBJSON"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -112,3 +114,5 @@ Linear in the size of the JSON value `j`.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid UBJSON.
|
||||
|
||||
@@ -21,17 +21,18 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
|
||||
| Compiler | Architecture | Operating System | CI |
|
||||
|----------------------------------------------|--------------|-----------------------------------|-----------|
|
||||
| AppleClang 15.0.0.15000040; Xcode 15.0.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000100; Xcode 15.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000100; Xcode 15.2 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000309; Xcode 15.3 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000309; Xcode 15.4 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000404; Xcode 26.1.1 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000603; Xcode 26.2 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000604; Xcode 26.3 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 21.0.0.21000099; Xcode 26.4.1 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| AppleClang 21.0.0.21000101; Xcode 26.5 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| AppleClang 21.0.0.21000101; Xcode 26.6 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
@@ -89,7 +90,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
||||
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
||||
|
||||
@@ -804,6 +804,20 @@ does not list an enumerator and it is therefore converted like the first listed
|
||||
[json.exception.type_error.318] duplicate object key 'red'
|
||||
```
|
||||
|
||||
### json.exception.type_error.321
|
||||
|
||||
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
|
||||
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
|
||||
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
|
||||
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback:
|
||||
```
|
||||
[json.exception.type_error.321] cannot serialize discarded value to CBOR
|
||||
```
|
||||
|
||||
## Out of range
|
||||
|
||||
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
||||
|
||||
@@ -125,23 +125,10 @@ meson wrap install nlohmann_json
|
||||
Please see the Meson project for any issues regarding the packaging.
|
||||
|
||||
The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in
|
||||
which case a [pkg-config](pkg-config.md) file and the CMake package config files are installed. To use it, have your build system require
|
||||
the `nlohmann_json` pkg-config dependency, or use [`find_package(nlohmann_json)`](cmake.md#external) in CMake. In Meson,
|
||||
it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a
|
||||
subproject fallback, rather than using the subproject directly.
|
||||
|
||||
The options that change the library's configuration are available in Meson as well, named like the
|
||||
[CMake options](cmake.md#cmake-options) without the `JSON_` prefix: `MultipleHeaders`, `GlobalUDLs`,
|
||||
`ImplicitConversions`, `DisableEnumSerialization`, `DisableTupleReferenceConversion`, `Diagnostics`,
|
||||
`Diagnostic_Positions`, `LegacyDiscardedValueComparison`, and `StrictNulHandling`. They have the same defaults as in CMake, except that
|
||||
`MultipleHeaders` is `false`. Set them with `-D` when setting up the build, or with the subproject name as prefix when
|
||||
the library is used as a subproject:
|
||||
|
||||
```shell
|
||||
meson setup build -Dnlohmann_json:Diagnostics=true
|
||||
```
|
||||
|
||||
The resulting compile definitions are part of the Meson dependency, the pkg-config file, and the CMake target.
|
||||
which case a [pkg-config](pkg-config.md) file is installed. To use it, have your build system require the
|
||||
`nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the
|
||||
[`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than
|
||||
using the subproject directly.
|
||||
|
||||
??? example "Example: Wrap"
|
||||
|
||||
|
||||
@@ -2021,6 +2021,39 @@ scan_number_done:
|
||||
// read the next character and ignore whitespace
|
||||
skip_whitespace();
|
||||
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan the next token when the caller expects a separator (':' or
|
||||
',') most of the time
|
||||
|
||||
After an object key the next token is almost always ':', after a value
|
||||
inside an object or array almost always ','. Testing for that character
|
||||
first is a compare and a well-predicted branch, where the switch in
|
||||
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||
goes through the switch, so the result is the same as scan()'s.
|
||||
|
||||
May only be called after scan() has run once (the BOM check is skipped).
|
||||
*/
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
{
|
||||
return expected_type;
|
||||
}
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
private:
|
||||
/// the part of scan() after the leading whitespace: skip comments and
|
||||
/// scan the token that starts with current
|
||||
token_type scan_after_whitespace()
|
||||
{
|
||||
// ignore comments
|
||||
while (ignore_comments && current == '/')
|
||||
{
|
||||
@@ -2100,7 +2133,6 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -423,7 +423,7 @@ class parser
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
// parse a new value
|
||||
get_token();
|
||||
@@ -463,7 +463,7 @@ class parser
|
||||
|
||||
// comma -> next value
|
||||
// or end of object (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
get_token();
|
||||
|
||||
@@ -484,7 +484,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -528,6 +528,13 @@ class parser
|
||||
return last_token = m_lexer.scan();
|
||||
}
|
||||
|
||||
/// get next token from lexer; true if it is the separator @a expected_type
|
||||
/// (name_separator or value_separator), which it usually is
|
||||
bool get_token_expecting(token_type expected_type)
|
||||
{
|
||||
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
|
||||
@@ -127,6 +127,7 @@ class binary_writer
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.317 if @a j is not an object
|
||||
@throw type_error.321 if a value nested in @a j is discarded
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -158,6 +159,7 @@ class binary_writer
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -322,7 +324,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "CBOR");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +384,7 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_msgpack(const BasicJsonType& j)
|
||||
{
|
||||
@@ -655,7 +658,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,6 +671,7 @@ class binary_writer
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -901,7 +905,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,6 +925,15 @@ class binary_writer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
{
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -1172,6 +1185,7 @@ class binary_writer
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
*/
|
||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -1198,10 +1212,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return 0ul;
|
||||
@@ -1238,10 +1254,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return;
|
||||
@@ -1308,6 +1326,8 @@ class binary_writer
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
@throw type_error.321 if a value nested in @a document is discarded,
|
||||
before anything is written
|
||||
*/
|
||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
|
||||
+210
-73
@@ -612,100 +612,239 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// constructor for rvalue binary arrays (internal type)
|
||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||
|
||||
void destroy(value_t t)
|
||||
private:
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The walk in destroy_container() may take the children of a
|
||||
// container in any order, as long as it picks the same child again
|
||||
// while that container is not modified in between. Arrays and
|
||||
// objects with bidirectional iterators (std::map, ordered_map, ...)
|
||||
// use their last child, which a vector-based container can remove
|
||||
// in O(1). ObjectType only needs forward iterators, though (e.g.
|
||||
// std::unordered_map), so other objects use their first child.
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::bidirectional_iterator_tag /*unused*/)
|
||||
{
|
||||
return std::prev(o.end());
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::forward_iterator_tag /*unused*/)
|
||||
{
|
||||
return o.begin();
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o)
|
||||
{
|
||||
JSON_ASSERT(!o.empty());
|
||||
return walk_child_it(o, typename std::iterator_traits<typename ObjectType_::iterator>::iterator_category());
|
||||
}
|
||||
|
||||
// the child of a non-empty array/object v that the walk in
|
||||
// destroy_container() continues with (see walk_child_it() above)
|
||||
static basic_json& walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
return v.m_data.m_value.array->back();
|
||||
}
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||
}
|
||||
|
||||
// removes walk_child(v) from a non-empty array/object v; this never
|
||||
// allocates, and since it is only ever called when that child is a
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// recurses more than one level deep (see destroy() below)
|
||||
static void pop_walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
v.m_data.m_value.array->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||
}
|
||||
}
|
||||
|
||||
// deallocates the (already empty) array/object held by v; this is
|
||||
// the same allocator-based free the old recursive implementation
|
||||
// used, just factored out so every level of the walk in destroy()
|
||||
// can share it
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
void destroy_container(value_t t) noexcept
|
||||
{
|
||||
if (
|
||||
(t == value_t::object && object == nullptr) ||
|
||||
(t == value_t::array && array == nullptr) ||
|
||||
(t == value_t::string && string == nullptr) ||
|
||||
(t == value_t::binary && binary == nullptr)
|
||||
(t == value_t::array && array == nullptr)
|
||||
)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
if (t == value_t::array || t == value_t::object)
|
||||
|
||||
// Destroy the tree without recursing per nesting level and
|
||||
// without any heap allocation: a heap-allocated flattening
|
||||
// stack (the previous implementation) can itself throw
|
||||
// bad_alloc, which would escape this noexcept destructor and
|
||||
// terminate the program (#5135).
|
||||
//
|
||||
// Instead, walk down a chain of children (always the one
|
||||
// walk_child() picks), reversing links as we go: cur is the
|
||||
// container currently being emptied, and prev is its parent
|
||||
// (value_t::null when there is none). Each parent's
|
||||
// walk_child() slot doubles as storage for that parent's own
|
||||
// parent link while we are below it, so no extra memory is
|
||||
// needed; the parent is not modified meanwhile, so
|
||||
// walk_child() finds that same slot again on the way up. We
|
||||
// only ever remove a child once it is a scalar or an empty
|
||||
// array/object, which neither allocates nor recurses more
|
||||
// than one level deep.
|
||||
//
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
while (true)
|
||||
{
|
||||
// flatten the current json_value to a heap-allocated stack
|
||||
std::vector<basic_json> stack;
|
||||
|
||||
// move the top-level items to stack
|
||||
if (t == value_t::array)
|
||||
if (has_no_children(cur))
|
||||
{
|
||||
stack.reserve(array->size());
|
||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.reserve(object->size());
|
||||
for (auto&& it : *object)
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
// move the last item to a local variable to be processed
|
||||
basic_json current_item(std::move(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
// if current_item is array/object, move
|
||||
// its children to the stack to be processed later
|
||||
if (current_item.is_array())
|
||||
{
|
||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||
|
||||
current_item.m_data.m_value.array->clear();
|
||||
}
|
||||
else if (current_item.is_object())
|
||||
{
|
||||
for (auto&& it : *current_item.m_data.m_value.object)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
|
||||
current_item.m_data.m_value.object->clear();
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
}
|
||||
|
||||
// it's now safe that current_item gets destructed
|
||||
// since it doesn't have any children
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// walk_child() slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
basic_json gp;
|
||||
take(gp, walk_child(prev));
|
||||
pop_walk_child(prev);
|
||||
|
||||
free_container(cur);
|
||||
|
||||
take(cur, prev);
|
||||
take(prev, gp);
|
||||
continue;
|
||||
}
|
||||
|
||||
basic_json& cur_child_ref = walk_child(cur);
|
||||
|
||||
if (has_no_children(cur_child_ref))
|
||||
{
|
||||
// scalar, or already-empty array/object
|
||||
pop_walk_child(cur);
|
||||
continue;
|
||||
}
|
||||
|
||||
// descend into the non-empty child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
basic_json tmp;
|
||||
take(tmp, cur_child_ref);
|
||||
take(cur_child_ref, prev);
|
||||
take(prev, cur);
|
||||
take(cur, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(value_t t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
destroy_string();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
destroy_binary();
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
destroy_container(t);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
@@ -714,9 +853,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+9
-99
@@ -2,114 +2,24 @@ project('nlohmann_json',
|
||||
'cpp',
|
||||
version : '3.12.0',
|
||||
license : 'MIT',
|
||||
meson_version : '>= 0.64',
|
||||
default_options: ['cpp_std=c++11'],
|
||||
)
|
||||
|
||||
if get_option('MultipleHeaders')
|
||||
incdir = 'include'
|
||||
else
|
||||
incdir = 'single_include'
|
||||
endif
|
||||
|
||||
# The same compile definitions as the CMake target (see target_compile_definitions
|
||||
# in CMakeLists.txt): only an option that differs from its default adds one.
|
||||
json_defines = []
|
||||
if not get_option('GlobalUDLs')
|
||||
json_defines += 'JSON_USE_GLOBAL_UDLS=0'
|
||||
endif
|
||||
if not get_option('ImplicitConversions')
|
||||
json_defines += 'JSON_USE_IMPLICIT_CONVERSIONS=0'
|
||||
endif
|
||||
if get_option('DisableEnumSerialization')
|
||||
json_defines += 'JSON_DISABLE_ENUM_SERIALIZATION=1'
|
||||
endif
|
||||
if get_option('DisableTupleReferenceConversion')
|
||||
json_defines += 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1'
|
||||
endif
|
||||
if get_option('Diagnostics')
|
||||
json_defines += 'JSON_DIAGNOSTICS=1'
|
||||
endif
|
||||
if get_option('Diagnostic_Positions')
|
||||
json_defines += 'JSON_DIAGNOSTIC_POSITIONS=1'
|
||||
endif
|
||||
if get_option('LegacyDiscardedValueComparison')
|
||||
json_defines += 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1'
|
||||
endif
|
||||
if get_option('StrictNulHandling')
|
||||
json_defines += 'JSON_STRICT_NUL_HANDLING=1'
|
||||
endif
|
||||
|
||||
cpp_args = []
|
||||
foreach define : json_defines
|
||||
cpp_args += '-D' + define
|
||||
endforeach
|
||||
|
||||
nlohmann_json_dep = declare_dependency(
|
||||
compile_args: cpp_args,
|
||||
include_directories: include_directories(incdir)
|
||||
include_directories: include_directories('single_include')
|
||||
)
|
||||
meson.override_dependency('nlohmann_json', nlohmann_json_dep)
|
||||
|
||||
# The multi-header version under the name earlier versions of this file used
|
||||
nlohmann_json_multiple_headers = declare_dependency(
|
||||
compile_args: cpp_args,
|
||||
include_directories: include_directories('include')
|
||||
)
|
||||
|
||||
if not meson.is_subproject()
|
||||
install_subdir(
|
||||
incdir / 'nlohmann',
|
||||
install_dir: get_option('includedir'),
|
||||
install_tag: 'devel',
|
||||
)
|
||||
install_headers('single_include/nlohmann/json.hpp', subdir: 'nlohmann')
|
||||
install_headers('single_include/nlohmann/json_fwd.hpp', subdir: 'nlohmann')
|
||||
install_headers('single_include/nlohmann/json_literals.hpp', subdir: 'nlohmann')
|
||||
|
||||
pkgc = import('pkgconfig')
|
||||
pkgc.generate(name: 'nlohmann_json',
|
||||
version: meson.project_version(),
|
||||
description: 'JSON for Modern C++',
|
||||
extra_cflags: cpp_args,
|
||||
install_dir: get_option('datadir') / 'pkgconfig',
|
||||
)
|
||||
|
||||
# CMake package config files, so that find_package(nlohmann_json) works. The
|
||||
# include directory is given relative to the config files, so that the
|
||||
# installation can be relocated. This is not possible if includedir or datadir
|
||||
# is an absolute path outside the prefix (e.g., with the separate outputs of
|
||||
# Nix); then the absolute include directory is used, as CMake does.
|
||||
fs = import('fs')
|
||||
cmake_install_dir = get_option('datadir') / 'cmake' / meson.project_name()
|
||||
cmake_to_prefix = []
|
||||
if fs.is_absolute(get_option('includedir')) or fs.is_absolute(cmake_install_dir)
|
||||
cmake_include_dir = (get_option('prefix') / get_option('includedir')).replace('\\', '/')
|
||||
else
|
||||
foreach component : cmake_install_dir.split('/')
|
||||
cmake_to_prefix += '..'
|
||||
endforeach
|
||||
cmake_include_dir = '${_IMPORT_PREFIX}/' + get_option('includedir')
|
||||
endif
|
||||
|
||||
cmake_conf = configuration_data()
|
||||
cmake_conf.set('PROJECT_NAME', meson.project_name())
|
||||
cmake_conf.set('PROJECT_VERSION', meson.project_version())
|
||||
cmake_conf.set('PROJECT_VERSION_MAJOR', meson.project_version().split('.')[0])
|
||||
cmake_conf.set('NLOHMANN_JSON_TARGET_NAME', meson.project_name())
|
||||
cmake_conf.set('NLOHMANN_JSON_TARGETS_EXPORT_NAME', meson.project_name() + 'Targets')
|
||||
cmake_conf.set('NLOHMANN_JSON_INCLUDE_DIR', cmake_include_dir)
|
||||
cmake_conf.set('NLOHMANN_JSON_CONFIG_TO_PREFIX', '/'.join(cmake_to_prefix))
|
||||
cmake_conf.set('NLOHMANN_JSON_COMPILE_DEFINITIONS', ';'.join(json_defines))
|
||||
|
||||
foreach cmake_file : [
|
||||
['cmake/config.cmake.in', 'nlohmann_jsonConfig.cmake'],
|
||||
['cmake/nlohmann_jsonConfigVersion.cmake.in', 'nlohmann_jsonConfigVersion.cmake'],
|
||||
['cmake/nlohmann_jsonTargets.cmake.in', 'nlohmann_jsonTargets.cmake'],
|
||||
]
|
||||
configure_file(
|
||||
input: cmake_file[0],
|
||||
output: cmake_file[1],
|
||||
configuration: cmake_conf,
|
||||
format: 'cmake@',
|
||||
install_dir: cmake_install_dir,
|
||||
)
|
||||
endforeach
|
||||
pkgc = import('pkgconfig')
|
||||
pkgc.generate(name: 'nlohmann_json',
|
||||
version: meson.project_version(),
|
||||
description: 'JSON for Modern C++'
|
||||
)
|
||||
endif
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
option(
|
||||
'MultipleHeaders',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Use non-amalgamated version of the library',
|
||||
)
|
||||
option(
|
||||
'GlobalUDLs',
|
||||
type: 'boolean',
|
||||
value: true,
|
||||
description: 'Place user-defined string literals in the global namespace',
|
||||
)
|
||||
option(
|
||||
'ImplicitConversions',
|
||||
type: 'boolean',
|
||||
value: true,
|
||||
description: 'Enable implicit conversions',
|
||||
)
|
||||
option(
|
||||
'DisableEnumSerialization',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Disable default integer enum serialization',
|
||||
)
|
||||
option(
|
||||
'DisableTupleReferenceConversion',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Disable conversion from a one-element tuple of a JSON reference',
|
||||
)
|
||||
option(
|
||||
'Diagnostics',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Use extended diagnostic messages',
|
||||
)
|
||||
option(
|
||||
'Diagnostic_Positions',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Enable diagnostic positions',
|
||||
)
|
||||
option(
|
||||
'LegacyDiscardedValueComparison',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Enable legacy discarded value comparison',
|
||||
)
|
||||
option(
|
||||
'StrictNulHandling',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
description: 'Enable strict NUL-byte handling',
|
||||
)
|
||||
@@ -12313,6 +12313,39 @@ scan_number_done:
|
||||
// read the next character and ignore whitespace
|
||||
skip_whitespace();
|
||||
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan the next token when the caller expects a separator (':' or
|
||||
',') most of the time
|
||||
|
||||
After an object key the next token is almost always ':', after a value
|
||||
inside an object or array almost always ','. Testing for that character
|
||||
first is a compare and a well-predicted branch, where the switch in
|
||||
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||
goes through the switch, so the result is the same as scan()'s.
|
||||
|
||||
May only be called after scan() has run once (the BOM check is skipped).
|
||||
*/
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
{
|
||||
return expected_type;
|
||||
}
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
private:
|
||||
/// the part of scan() after the leading whitespace: skip comments and
|
||||
/// scan the token that starts with current
|
||||
token_type scan_after_whitespace()
|
||||
{
|
||||
// ignore comments
|
||||
while (ignore_comments && current == '/')
|
||||
{
|
||||
@@ -12392,7 +12425,6 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
|
||||
@@ -18422,7 +18454,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -18585,7 +18617,7 @@ class parser
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
// parse a new value
|
||||
get_token();
|
||||
@@ -18625,7 +18657,7 @@ class parser
|
||||
|
||||
// comma -> next value
|
||||
// or end of object (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
get_token();
|
||||
|
||||
@@ -18646,7 +18678,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -18690,6 +18722,13 @@ class parser
|
||||
return last_token = m_lexer.scan();
|
||||
}
|
||||
|
||||
/// get next token from lexer; true if it is the separator @a expected_type
|
||||
/// (name_separator or value_separator), which it usually is
|
||||
bool get_token_expecting(token_type expected_type)
|
||||
{
|
||||
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
@@ -21587,6 +21626,7 @@ class binary_writer
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.317 if @a j is not an object
|
||||
@throw type_error.321 if a value nested in @a j is discarded
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21618,6 +21658,7 @@ class binary_writer
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21782,7 +21823,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "CBOR");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21842,6 +21883,7 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_msgpack(const BasicJsonType& j)
|
||||
{
|
||||
@@ -22115,7 +22157,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22128,6 +22170,7 @@ class binary_writer
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -22361,7 +22404,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22381,6 +22424,15 @@ class binary_writer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
{
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -22632,6 +22684,7 @@ class binary_writer
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
*/
|
||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -22658,10 +22711,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return 0ul;
|
||||
@@ -22698,10 +22753,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return;
|
||||
@@ -22768,6 +22825,8 @@ class binary_writer
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
@throw type_error.321 if a value nested in @a document is discarded,
|
||||
before anything is written
|
||||
*/
|
||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
@@ -27792,100 +27851,239 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// constructor for rvalue binary arrays (internal type)
|
||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||
|
||||
void destroy(value_t t)
|
||||
private:
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The walk in destroy_container() may take the children of a
|
||||
// container in any order, as long as it picks the same child again
|
||||
// while that container is not modified in between. Arrays and
|
||||
// objects with bidirectional iterators (std::map, ordered_map, ...)
|
||||
// use their last child, which a vector-based container can remove
|
||||
// in O(1). ObjectType only needs forward iterators, though (e.g.
|
||||
// std::unordered_map), so other objects use their first child.
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::bidirectional_iterator_tag /*unused*/)
|
||||
{
|
||||
return std::prev(o.end());
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::forward_iterator_tag /*unused*/)
|
||||
{
|
||||
return o.begin();
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o)
|
||||
{
|
||||
JSON_ASSERT(!o.empty());
|
||||
return walk_child_it(o, typename std::iterator_traits<typename ObjectType_::iterator>::iterator_category());
|
||||
}
|
||||
|
||||
// the child of a non-empty array/object v that the walk in
|
||||
// destroy_container() continues with (see walk_child_it() above)
|
||||
static basic_json& walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
return v.m_data.m_value.array->back();
|
||||
}
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||
}
|
||||
|
||||
// removes walk_child(v) from a non-empty array/object v; this never
|
||||
// allocates, and since it is only ever called when that child is a
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// recurses more than one level deep (see destroy() below)
|
||||
static void pop_walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
v.m_data.m_value.array->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||
}
|
||||
}
|
||||
|
||||
// deallocates the (already empty) array/object held by v; this is
|
||||
// the same allocator-based free the old recursive implementation
|
||||
// used, just factored out so every level of the walk in destroy()
|
||||
// can share it
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
void destroy_container(value_t t) noexcept
|
||||
{
|
||||
if (
|
||||
(t == value_t::object && object == nullptr) ||
|
||||
(t == value_t::array && array == nullptr) ||
|
||||
(t == value_t::string && string == nullptr) ||
|
||||
(t == value_t::binary && binary == nullptr)
|
||||
(t == value_t::array && array == nullptr)
|
||||
)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
if (t == value_t::array || t == value_t::object)
|
||||
|
||||
// Destroy the tree without recursing per nesting level and
|
||||
// without any heap allocation: a heap-allocated flattening
|
||||
// stack (the previous implementation) can itself throw
|
||||
// bad_alloc, which would escape this noexcept destructor and
|
||||
// terminate the program (#5135).
|
||||
//
|
||||
// Instead, walk down a chain of children (always the one
|
||||
// walk_child() picks), reversing links as we go: cur is the
|
||||
// container currently being emptied, and prev is its parent
|
||||
// (value_t::null when there is none). Each parent's
|
||||
// walk_child() slot doubles as storage for that parent's own
|
||||
// parent link while we are below it, so no extra memory is
|
||||
// needed; the parent is not modified meanwhile, so
|
||||
// walk_child() finds that same slot again on the way up. We
|
||||
// only ever remove a child once it is a scalar or an empty
|
||||
// array/object, which neither allocates nor recurses more
|
||||
// than one level deep.
|
||||
//
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
while (true)
|
||||
{
|
||||
// flatten the current json_value to a heap-allocated stack
|
||||
std::vector<basic_json> stack;
|
||||
|
||||
// move the top-level items to stack
|
||||
if (t == value_t::array)
|
||||
if (has_no_children(cur))
|
||||
{
|
||||
stack.reserve(array->size());
|
||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.reserve(object->size());
|
||||
for (auto&& it : *object)
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
// move the last item to a local variable to be processed
|
||||
basic_json current_item(std::move(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
// if current_item is array/object, move
|
||||
// its children to the stack to be processed later
|
||||
if (current_item.is_array())
|
||||
{
|
||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||
|
||||
current_item.m_data.m_value.array->clear();
|
||||
}
|
||||
else if (current_item.is_object())
|
||||
{
|
||||
for (auto&& it : *current_item.m_data.m_value.object)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
|
||||
current_item.m_data.m_value.object->clear();
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
}
|
||||
|
||||
// it's now safe that current_item gets destructed
|
||||
// since it doesn't have any children
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// walk_child() slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
basic_json gp;
|
||||
take(gp, walk_child(prev));
|
||||
pop_walk_child(prev);
|
||||
|
||||
free_container(cur);
|
||||
|
||||
take(cur, prev);
|
||||
take(prev, gp);
|
||||
continue;
|
||||
}
|
||||
|
||||
basic_json& cur_child_ref = walk_child(cur);
|
||||
|
||||
if (has_no_children(cur_child_ref))
|
||||
{
|
||||
// scalar, or already-empty array/object
|
||||
pop_walk_child(cur);
|
||||
continue;
|
||||
}
|
||||
|
||||
// descend into the non-empty child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
basic_json tmp;
|
||||
take(tmp, cur_child_ref);
|
||||
take(cur_child_ref, prev);
|
||||
take(prev, cur);
|
||||
take(cur, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(value_t t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
destroy_string();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
destroy_binary();
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
destroy_container(t);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
@@ -27894,9 +28092,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -607,3 +607,88 @@ TEST_CASE("bad my_allocator::construct")
|
||||
j["test"].push_back("should not leak");
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t counting_allocator_allocations = 0;
|
||||
std::size_t counting_allocator_deallocations = 0;
|
||||
|
||||
template<class T>
|
||||
struct counting_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
++counting_allocator_allocations;
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
++counting_allocator_deallocations;
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = counting_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("destructor performs no allocation, only deallocation")
|
||||
{
|
||||
// see https://github.com/nlohmann/json/issues/4842 and
|
||||
// https://github.com/nlohmann/json/issues/5135: destroying nested
|
||||
// arrays/objects used to allocate a temporary stack (first with
|
||||
// std::allocator, later - after #4842 - with the provided allocator).
|
||||
// Since that stack could itself throw bad_alloc from inside the
|
||||
// noexcept destructor (#5135), destroy() no longer allocates anything:
|
||||
// it only ever frees what is already there.
|
||||
using counting_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
counting_allocator>;
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
|
||||
SECTION("mixed tree of empty/non-empty arrays and objects")
|
||||
{
|
||||
auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory)
|
||||
{
|
||||
{"empty_obj", counting_json::object()},
|
||||
{"empty_arr", counting_json::array()},
|
||||
{"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}},
|
||||
{"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})}
|
||||
});
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,10 +114,59 @@ TEST_CASE("BJData")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to BJData
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_bjdata(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("optimized array of all-discarded elements")
|
||||
{
|
||||
json const j = {discarded, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
|
||||
@@ -149,6 +149,54 @@ TEST_CASE("BSON")
|
||||
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
json const j = json::value_t::discarded;
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is discarded", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container cannot be serialized to BSON")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("in an array that is an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = json::array({1, discarded, 2});
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/a/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in object)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json j;
|
||||
j["outer"] = middle_object;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/outer/x/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
|
||||
|
||||
+42
-3
@@ -38,10 +38,49 @@ TEST_CASE("CBOR")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to CBOR
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_cbor(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("NaN")
|
||||
|
||||
@@ -2317,6 +2317,58 @@ TEST_CASE("parser class")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("comments before separators")
|
||||
{
|
||||
// The parser first checks for the expected ':' or ',' and only then
|
||||
// falls back to the full token switch, which skips comments. A comment
|
||||
// directly before a separator takes that fallback.
|
||||
json _;
|
||||
|
||||
SECTION("ignored")
|
||||
{
|
||||
const std::vector<std::pair<std::string, json>> inputs =
|
||||
{
|
||||
{"{\"a\" /* c */ : 1}", {{"a", 1}}},
|
||||
{"{\"a\" // c\n: 1}", {{"a", 1}}},
|
||||
{R"({"a": 1, "b" /* c */ : 2})", {{"a", 1}, {"b", 2}}},
|
||||
{R"({"a": 1 /* c */ , "b": 2})", {{"a", 1}, {"b", 2}}},
|
||||
{"{\"a\": 1 // c\n, \"b\": 2}", {{"a", 1}, {"b", 2}}},
|
||||
{"[1 /* c */ , 2]", {1, 2}},
|
||||
{"[1 // c\n, 2]", {1, 2}},
|
||||
{"{\"a\" /* c */ /* d */ : [1 // c\n , 2 /**/ ] /**/ , \"b\" : 3}", {{"a", {1, 2}}, {"b", 3}}}
|
||||
};
|
||||
for (const auto& input : inputs)
|
||||
{
|
||||
CAPTURE(input.first)
|
||||
CHECK(json::parse(input.first, nullptr, true, true) == input.second);
|
||||
CHECK(json::accept(input.first, true));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ignored, with trailing commas")
|
||||
{
|
||||
CHECK(json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true, true) == json({1}));
|
||||
CHECK(json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true, true) == json({{"a", 1}}));
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 19: syntax error while parsing object key - unexpected '}'; expected string literal", json::parse_error);
|
||||
}
|
||||
|
||||
SECTION("not ignored")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\" /* c */ : 1}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing object separator - invalid literal; last read: '\"a\" /'; expected ':'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1, \"b\" /* c */ : 2}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing object separator - invalid literal; last read: '\"b\" /'; expected ':'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , \"b\": 2}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 9: syntax error while parsing object - invalid literal; last read: '1 /'; expected '}'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , 2]")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing array - invalid literal; last read: '1 /'; expected ']'", json::parse_error);
|
||||
CHECK(!json::accept(std::string("[1 /* c */ , 2]")));
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Macro for all test cases for start_pos and end_pos
|
||||
#define SETUP_TESTCASES() \
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -196,6 +198,198 @@ struct void_erase_map : std::map<Key, T, Compare, Allocator>
|
||||
|
||||
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
||||
|
||||
// wraps an iterator, but only offers the LegacyForwardIterator operations,
|
||||
// like the iterators of std::unordered_map and other hash maps
|
||||
template<class BaseIterator>
|
||||
class forward_only_iterator
|
||||
{
|
||||
BaseIterator m_it{};
|
||||
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = typename std::iterator_traits<BaseIterator>::value_type;
|
||||
using difference_type = typename std::iterator_traits<BaseIterator>::difference_type;
|
||||
using pointer = typename std::iterator_traits<BaseIterator>::pointer;
|
||||
using reference = typename std::iterator_traits<BaseIterator>::reference;
|
||||
|
||||
forward_only_iterator() = default;
|
||||
explicit forward_only_iterator(BaseIterator it) : m_it(it) {}
|
||||
|
||||
BaseIterator base() const
|
||||
{
|
||||
return m_it;
|
||||
}
|
||||
|
||||
reference operator*() const
|
||||
{
|
||||
return *m_it;
|
||||
}
|
||||
pointer operator->() const
|
||||
{
|
||||
return &*m_it;
|
||||
}
|
||||
forward_only_iterator& operator++()
|
||||
{
|
||||
++m_it;
|
||||
return *this;
|
||||
}
|
||||
forward_only_iterator operator++(int)
|
||||
{
|
||||
auto result = *this;
|
||||
++m_it;
|
||||
return result;
|
||||
}
|
||||
|
||||
friend bool operator==(const forward_only_iterator& lhs, const forward_only_iterator& rhs)
|
||||
{
|
||||
return lhs.m_it == rhs.m_it;
|
||||
}
|
||||
friend bool operator!=(const forward_only_iterator& lhs, const forward_only_iterator& rhs)
|
||||
{
|
||||
return lhs.m_it != rhs.m_it;
|
||||
}
|
||||
};
|
||||
|
||||
// An ObjectType whose iterators are forward-only, as those of hash maps are;
|
||||
// it has no rbegin() and its iterators no operator--. A hash map is not used
|
||||
// directly for the same reason as in no_key_compare_map above.
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
class forward_only_map
|
||||
{
|
||||
using map_t = std::map<Key, T, Compare, Allocator>;
|
||||
map_t data;
|
||||
|
||||
public:
|
||||
using key_type = typename map_t::key_type;
|
||||
using mapped_type = typename map_t::mapped_type;
|
||||
using value_type = typename map_t::value_type;
|
||||
using size_type = typename map_t::size_type;
|
||||
using allocator_type = typename map_t::allocator_type;
|
||||
using iterator = forward_only_iterator<typename map_t::iterator>;
|
||||
using const_iterator = forward_only_iterator<typename map_t::const_iterator>;
|
||||
|
||||
forward_only_map() noexcept(std::is_nothrow_default_constructible<map_t>::value) : data() {}
|
||||
|
||||
template<class InputIt>
|
||||
forward_only_map(InputIt first, InputIt last) : data(first, last) {}
|
||||
|
||||
iterator begin() noexcept
|
||||
{
|
||||
return iterator(data.begin());
|
||||
}
|
||||
iterator end() noexcept
|
||||
{
|
||||
return iterator(data.end());
|
||||
}
|
||||
const_iterator begin() const noexcept
|
||||
{
|
||||
return const_iterator(data.begin());
|
||||
}
|
||||
const_iterator end() const noexcept
|
||||
{
|
||||
return const_iterator(data.end());
|
||||
}
|
||||
const_iterator cbegin() const noexcept
|
||||
{
|
||||
return const_iterator(data.cbegin());
|
||||
}
|
||||
const_iterator cend() const noexcept
|
||||
{
|
||||
return const_iterator(data.cend());
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return data.empty();
|
||||
}
|
||||
size_type size() const noexcept
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
size_type max_size() const noexcept
|
||||
{
|
||||
return data.max_size();
|
||||
}
|
||||
void clear() noexcept
|
||||
{
|
||||
data.clear();
|
||||
}
|
||||
|
||||
iterator find(const key_type& key)
|
||||
{
|
||||
return iterator(data.find(key));
|
||||
}
|
||||
const_iterator find(const key_type& key) const
|
||||
{
|
||||
return const_iterator(data.find(key));
|
||||
}
|
||||
size_type count(const key_type& key) const
|
||||
{
|
||||
return data.count(key);
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> emplace(const key_type& key, const mapped_type& value)
|
||||
{
|
||||
const auto result = data.emplace(key, value);
|
||||
return {iterator(result.first), result.second};
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> insert(const value_type& value)
|
||||
{
|
||||
const auto result = data.insert(value);
|
||||
return {iterator(result.first), result.second};
|
||||
}
|
||||
|
||||
template<class InputIt>
|
||||
void insert(InputIt first, InputIt last)
|
||||
{
|
||||
data.insert(first, last);
|
||||
}
|
||||
|
||||
mapped_type& operator[](const key_type& key)
|
||||
{
|
||||
return data[key];
|
||||
}
|
||||
|
||||
mapped_type& at(const key_type& key)
|
||||
{
|
||||
return data.at(key);
|
||||
}
|
||||
const mapped_type& at(const key_type& key) const
|
||||
{
|
||||
return data.at(key);
|
||||
}
|
||||
|
||||
iterator erase(iterator pos)
|
||||
{
|
||||
return iterator(data.erase(pos.base()));
|
||||
}
|
||||
iterator erase(iterator first, iterator last)
|
||||
{
|
||||
return iterator(data.erase(first.base(), last.base()));
|
||||
}
|
||||
size_type erase(const key_type& key)
|
||||
{
|
||||
return data.erase(key);
|
||||
}
|
||||
|
||||
void swap(forward_only_map& other) noexcept(noexcept(data.swap(other.data)))
|
||||
{
|
||||
data.swap(other.data);
|
||||
}
|
||||
|
||||
friend bool operator==(const forward_only_map& lhs, const forward_only_map& rhs)
|
||||
{
|
||||
return lhs.data == rhs.data;
|
||||
}
|
||||
friend bool operator<(const forward_only_map& lhs, const forward_only_map& rhs)
|
||||
{
|
||||
return lhs.data < rhs.data;
|
||||
}
|
||||
};
|
||||
|
||||
using forward_only_json = nlohmann::basic_json<forward_only_map>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("object type whose erase() returns void")
|
||||
@@ -322,3 +516,46 @@ TEST_CASE("object type without key_compare")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("object type with forward-only iterators")
|
||||
{
|
||||
CHECK(std::is_same<std::iterator_traits<forward_only_json::object_t::iterator>::iterator_category,
|
||||
std::forward_iterator_tag>::value);
|
||||
|
||||
SECTION("destroying nested objects and arrays")
|
||||
{
|
||||
forward_only_json j;
|
||||
j["a"] = 1;
|
||||
j["b"]["c"] = "x";
|
||||
j["b"]["d"] = forward_only_json::array();
|
||||
j["b"]["d"].push_back(forward_only_json::object());
|
||||
j["b"]["d"].push_back(true);
|
||||
j["b"]["e"]["f"]["g"] = nullptr;
|
||||
j["h"] = forward_only_json::object();
|
||||
j["i"]["j"] = 2;
|
||||
|
||||
CHECK(j.size() == 4);
|
||||
CHECK(j["b"].size() == 3);
|
||||
CHECK(j["b"]["d"].size() == 2);
|
||||
CHECK(j["b"]["e"]["f"]["g"].is_null());
|
||||
|
||||
CHECK(j.erase("b") == 1);
|
||||
CHECK(j.size() == 3);
|
||||
j = 42;
|
||||
CHECK(j == 42);
|
||||
}
|
||||
|
||||
SECTION("destroying a deeply nested object")
|
||||
{
|
||||
constexpr std::size_t depth = 100000;
|
||||
forward_only_json j;
|
||||
forward_only_json* cur = &j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
(*cur)["s"] = i;
|
||||
cur = &(*cur)["o"];
|
||||
}
|
||||
CHECK(j["o"]["o"]["s"] == 2);
|
||||
// destroyed at the end of scope without recursing per level
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,49 @@ TEST_CASE("MessagePack")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to MessagePack
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_msgpack(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
|
||||
@@ -40,7 +40,9 @@ using ordered_json = nlohmann::ordered_json;
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <list>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -107,6 +109,84 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// An allocator whose allocate() can be told to fail on demand, so tests can
|
||||
// check that ~basic_json() tolerates - in fact, after #5135, never even
|
||||
// triggers - an allocation failure. This replaces an earlier version of
|
||||
// this test that overrode the process-wide ::operator new/::operator
|
||||
// delete, which affected every allocation in the whole unit-regression2
|
||||
// binary rather than just the values under test.
|
||||
std::size_t failing_allocator_allocations = 0;
|
||||
std::size_t failing_allocator_deallocations = 0;
|
||||
bool fail_next_allocation = false;
|
||||
|
||||
template<class T>
|
||||
struct failing_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
failing_allocator() noexcept = default;
|
||||
template<class U>
|
||||
failing_allocator(const failing_allocator<U>& /*unused*/) noexcept {} // NOLINT(google-explicit-constructor)
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
if (fail_next_allocation)
|
||||
{
|
||||
fail_next_allocation = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
++failing_allocator_allocations;
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
++failing_allocator_deallocations;
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = failing_allocator<U>;
|
||||
};
|
||||
};
|
||||
|
||||
using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||
using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
|
||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||
|
||||
// builds `depth` levels of nesting around a scalar, iteratively (never
|
||||
// recursing: each wrap only moves the previous, already-built value, which
|
||||
// is O(1)), each level an array or an object depending on `nest_objects`
|
||||
template<class BasicJsonType>
|
||||
BasicJsonType make_deep_nest(std::size_t depth, bool nest_objects)
|
||||
{
|
||||
BasicJsonType v = 0;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (nest_objects)
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::object();
|
||||
wrapper["x"] = std::move(v);
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::array();
|
||||
wrapper.push_back(std::move(v));
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #1647
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -940,4 +1020,211 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("regression test #5135 - destructor never allocates, even under memory pressure")
|
||||
{
|
||||
// Before the fix, ~basic_json() flattened a nested array/object into a
|
||||
// heap-allocated std::vector to avoid recursing; that allocation could
|
||||
// itself throw bad_alloc, which escapes a noexcept destructor and
|
||||
// terminates the program. destroy() no longer allocates anything, so
|
||||
// none of the sections below ever observe fail_next_allocation being
|
||||
// consumed: CHECK(fail_next_allocation) confirms it was never touched.
|
||||
|
||||
SECTION("the original report: a small, mixed array/object nest")
|
||||
{
|
||||
failing_allocator_allocations = 0;
|
||||
failing_allocator_deallocations = 0;
|
||||
{
|
||||
failing_json j = failing_json::array(
|
||||
{
|
||||
failing_json::array({1, 2}),
|
||||
failing_json::object({{"key", failing_json::array({3})}})
|
||||
});
|
||||
fail_next_allocation = true;
|
||||
} // j is destroyed here, with every further allocation set to fail
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_deallocations > 0);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested array")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json j = make_deep_nest<failing_json>(100000, false);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested object")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json j = make_deep_nest<failing_json>(100000, true);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested ordered_json")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_ordered_json j = make_deep_nest<failing_ordered_json>(100000, true);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("wide and deep: 1000 arrays of 1000 elements, each a small nested object")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json wide = failing_json::array();
|
||||
for (std::size_t i = 0; i < 1000; ++i)
|
||||
{
|
||||
failing_json inner = failing_json::array();
|
||||
for (std::size_t k = 0; k < 1000; ++k)
|
||||
{
|
||||
inner.push_back(failing_json::object({{"a", 1}, {"b", failing_json::array({1, 2, 3})}}));
|
||||
}
|
||||
wide.push_back(std::move(inner));
|
||||
}
|
||||
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// a single-element chain of `depth` arrays, built iteratively (never
|
||||
// recursing: each wrap only moves the previous, already-built value)
|
||||
template<class BasicJsonType>
|
||||
BasicJsonType make_single_chain(std::size_t depth)
|
||||
{
|
||||
BasicJsonType v = 1;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::array();
|
||||
wrapper.push_back(std::move(v));
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// copies value first, to make sure nothing was corrupted by building it,
|
||||
// then lets both the copy and the original destruct via normal scope exit
|
||||
template<class BasicJsonType>
|
||||
void check_destroy_edge_case(const BasicJsonType& value)
|
||||
{
|
||||
const BasicJsonType copy = value;
|
||||
CHECK(copy == value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType, json, ordered_json)
|
||||
{
|
||||
using binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
SECTION("mix of empty objects, empty arrays, non-empty containers, and scalars")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
root.push_back(BasicJsonType::object());
|
||||
root.push_back(BasicJsonType::array());
|
||||
root.push_back(BasicJsonType::object({{"k", 1}}));
|
||||
root.push_back(BasicJsonType::array({1, 2, 3}));
|
||||
root.push_back(nullptr);
|
||||
root.push_back(true);
|
||||
root.push_back(42);
|
||||
root.push_back(3.14);
|
||||
root.push_back("a string");
|
||||
root.push_back(BasicJsonType(binary_t({1, 2, 3})));
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container child in first position only")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1, 2}), 3, 4, 5});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container child in last position only")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({1, 2, 3, BasicJsonType::array({4, 5})});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container children in first and last position")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1}), 2, 3, BasicJsonType::array({4})});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("single-element chain, 1000 levels deep")
|
||||
{
|
||||
BasicJsonType root = make_single_chain<BasicJsonType>(1000);
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("top-level empty array")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("top-level empty object")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::object();
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("object whose last child is a non-empty array whose last child is an empty object")
|
||||
{
|
||||
BasicJsonType inner_array = BasicJsonType::array({1, 2, BasicJsonType::object()});
|
||||
BasicJsonType root = BasicJsonType::object({{"a", 1}, {"b", inner_array}});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("destruction via erase() on a deeply nested child")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
root.push_back(make_single_chain<BasicJsonType>(500));
|
||||
root.push_back(BasicJsonType::object({{"k", BasicJsonType::array({1, 2, 3})}}));
|
||||
// erase() must destroy the removed subtree without recursing or
|
||||
// allocating beyond what erase() itself needs
|
||||
root.erase(0);
|
||||
CAPTURE(root.size())
|
||||
CHECK(root.size() == 1);
|
||||
}
|
||||
|
||||
SECTION("destruction via assignment on a deep tree")
|
||||
{
|
||||
BasicJsonType root = make_single_chain<BasicJsonType>(2000);
|
||||
// assigning a new value destroys the old one in place
|
||||
root = nullptr;
|
||||
CHECK(root.is_null());
|
||||
}
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
|
||||
@@ -35,10 +35,59 @@ TEST_CASE("UBJSON")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to UBJSON
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_ubjson(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("optimized array of all-discarded elements")
|
||||
{
|
||||
json const j = {discarded, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
@@ -2099,9 +2148,14 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
// a discarded value cannot be serialized to UBJSON, even as part
|
||||
// of an optimized array of a single (here: valueless) type
|
||||
json const j = {json::value_t::discarded, json::value_t::discarded};
|
||||
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
|
||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# check_build_options
|
||||
|
||||
Checks that the Meson build and the pkg-config files offer the same options as the CMake target, so that a new CMake
|
||||
option is not forgotten in one of them.
|
||||
|
||||
The compile definitions of the CMake target (`target_compile_definitions` in [`CMakeLists.txt`](../../CMakeLists.txt))
|
||||
are the reference. When you add an option there, also add it to
|
||||
|
||||
- the pkg-config block in `CMakeLists.txt` (`NLOHMANN_JSON_PKGCONFIG_CFLAGS`),
|
||||
- [`meson_options.txt`](../../meson_options.txt), named without the `JSON_` prefix and with the same default,
|
||||
- [`meson.build`](../../meson.build) (`json_defines`), and
|
||||
- the list of Meson options in
|
||||
[`docs/mkdocs/docs/integration/package_managers.md`](../../docs/mkdocs/docs/integration/package_managers.md).
|
||||
|
||||
Run the check with
|
||||
|
||||
```shell
|
||||
make check_build_options
|
||||
```
|
||||
|
||||
It needs only Python 3 and runs in the `ci_meson_install` CI job.
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check that the Meson build and the pkg-config files offer the CMake options.
|
||||
|
||||
The compile definitions of the CMake target (target_compile_definitions in
|
||||
CMakeLists.txt) are the reference. For every option used there, the script
|
||||
checks that
|
||||
|
||||
- the CMake pkg-config file adds the same definition under the same condition,
|
||||
- meson_options.txt has a boolean option of the same name without the "JSON_"
|
||||
prefix and with the same default,
|
||||
- meson.build adds the same definition under the same condition, and
|
||||
- the Meson section of the package manager documentation lists the option.
|
||||
|
||||
Meson's MultipleHeaders option selects the include directory and adds no
|
||||
definition; it is the only Meson option without a definition.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..'))
|
||||
DOCS = os.path.join('docs', 'mkdocs', 'docs', 'integration', 'package_managers.md')
|
||||
|
||||
# Meson options that add no compile definition, with their default
|
||||
MESON_ONLY = {'MultipleHeaders': 'false'}
|
||||
|
||||
|
||||
def read(root, path):
|
||||
with open(os.path.join(root, path), encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def cmake_target_definitions(cmake):
|
||||
"""Return {option: (definition, add_if_on)} from target_compile_definitions."""
|
||||
block = re.search(r'target_compile_definitions\(\s*\$\{NLOHMANN_JSON_TARGET_NAME\}\s*INTERFACE(.*?)\n\)', cmake, re.S)
|
||||
if not block:
|
||||
sys.exit('CMakeLists.txt: target_compile_definitions of the target not found')
|
||||
result = {}
|
||||
for line in block.group(1).split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
m = re.fullmatch(r'\$<\$<NOT:\$<BOOL:\$\{JSON_(\w+)\}>>:(\w+=\w+)>', line)
|
||||
if m:
|
||||
result[m.group(1)] = (m.group(2), False)
|
||||
continue
|
||||
m = re.fullmatch(r'\$<\$<BOOL:\$\{JSON_(\w+)\}>:(\w+=\w+)>', line)
|
||||
if m:
|
||||
result[m.group(1)] = (m.group(2), True)
|
||||
continue
|
||||
sys.exit(f'CMakeLists.txt: unexpected line in target_compile_definitions: {line}')
|
||||
return result
|
||||
|
||||
|
||||
def cmake_defaults(cmake):
|
||||
"""Return {option: 'true'/'false'} for the option() calls of the form JSON_<name>."""
|
||||
return {m.group(1): 'true' if m.group(2) == 'ON' else 'false'
|
||||
for m in re.finditer(r'^option\(JSON_(\w+)\s+"[^"]*"\s+(ON|OFF)\)', cmake, re.M)}
|
||||
|
||||
|
||||
def cmake_pkgconfig_definitions(cmake):
|
||||
"""Return {option: (definition, add_if_on)} from the pkg-config block."""
|
||||
return {m.group(2): (m.group(3), m.group(1) is None)
|
||||
for m in re.finditer(r'if \((NOT )?JSON_(\w+)\)\s*\n\s*string\(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -D(\w+=\w+)"\)', cmake)}
|
||||
|
||||
|
||||
def meson_options(options):
|
||||
"""Return {option: default} for the boolean options in meson_options.txt."""
|
||||
result = {}
|
||||
for block in re.findall(r'option\((.*?)\)', options, re.S):
|
||||
name = re.search(r"'(\w+)'", block).group(1)
|
||||
kind = re.search(r"type\s*:\s*'(\w+)'", block)
|
||||
value = re.search(r'value\s*:\s*(\w+)', block)
|
||||
result[name] = value.group(1) if kind and kind.group(1) == 'boolean' and value else None
|
||||
return result
|
||||
|
||||
|
||||
def meson_definitions(meson):
|
||||
"""Return {option: (definition, add_if_on)} from meson.build."""
|
||||
return {m.group(2): (m.group(3), m.group(1) is None)
|
||||
for m in re.finditer(r"if (not )?get_option\('(\w+)'\)\s*\n\s*json_defines \+= '(\w+=\w+)'", meson)}
|
||||
|
||||
|
||||
def describe(definition):
|
||||
name, add_if_on = definition
|
||||
return f'{name} if {"enabled" if add_if_on else "disabled"}'
|
||||
|
||||
|
||||
def compare(errors, where, expected, actual):
|
||||
for option, definition in expected.items():
|
||||
if option not in actual:
|
||||
errors.append(f'{where}: no definition for option {option} (expected {describe(definition)})')
|
||||
elif actual[option] != definition:
|
||||
errors.append(f'{where}: option {option} adds {describe(actual[option])}, expected {describe(definition)}')
|
||||
for option in actual.keys() - expected.keys():
|
||||
errors.append(f'{where}: definition for option {option}, which the CMake target does not have')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.split('\n')[0])
|
||||
parser.add_argument('root', nargs='?', default=REPO_ROOT, help='repository root (default: %(default)s)')
|
||||
root = parser.parse_args().root
|
||||
|
||||
cmake = read(root, 'CMakeLists.txt')
|
||||
reference = cmake_target_definitions(cmake)
|
||||
defaults = cmake_defaults(cmake)
|
||||
errors = []
|
||||
|
||||
compare(errors, 'CMakeLists.txt (pkg-config)', reference, cmake_pkgconfig_definitions(cmake))
|
||||
compare(errors, 'meson.build', reference, meson_definitions(read(root, 'meson.build')))
|
||||
|
||||
options = meson_options(read(root, 'meson_options.txt'))
|
||||
for option in reference:
|
||||
if option not in defaults:
|
||||
errors.append(f'CMakeLists.txt: no option(JSON_{option} ... ON|OFF)')
|
||||
elif option not in options:
|
||||
errors.append(f'meson_options.txt: option {option} missing')
|
||||
elif options[option] != defaults[option]:
|
||||
errors.append(f'meson_options.txt: option {option} must be boolean with value {defaults[option]} as in CMake')
|
||||
for option, default in MESON_ONLY.items():
|
||||
if options.get(option) != default:
|
||||
errors.append(f'meson_options.txt: option {option} must be boolean with value {default}')
|
||||
for option in options.keys() - reference.keys() - MESON_ONLY.keys():
|
||||
errors.append(f'meson_options.txt: option {option} has no counterpart in the CMake target')
|
||||
|
||||
docs = read(root, DOCS)
|
||||
for option in sorted(set(options) & (reference.keys() | MESON_ONLY.keys())):
|
||||
if f'`{option}`' not in docs:
|
||||
errors.append(f'{DOCS}: Meson option {option} not listed')
|
||||
|
||||
for error in errors:
|
||||
print(error, file=sys.stderr)
|
||||
if errors:
|
||||
print('The Meson build and the pkg-config files must offer the options of the CMake target; see '
|
||||
'tools/check_build_options/README.md.', file=sys.stderr)
|
||||
return 1
|
||||
print(f'OK: {len(reference)} options with compile definitions agree between CMake, pkg-config, and Meson.')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user