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 |
No files matched your search
@@ -17,11 +17,11 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macos-14:
|
macos-15:
|
||||||
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
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:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
macos-15:
|
macos-26:
|
||||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
xcode: ['26.4.1', '26.5', '26.6']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
|
|||||||
@@ -31,48 +31,6 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --target ci_test_gcc
|
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:
|
ci_infer:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -251,7 +209,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# older GCC docker images (4, 5, 6) fail to check out code
|
# 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 }}
|
container: gcc:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|||||||
+1
-27
@@ -186,33 +186,7 @@ if (MSVC)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Install a pkg-config file, so other tools can find this. It carries the same
|
# Install a pkg-config file, so other tools can find this.
|
||||||
# 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()
|
|
||||||
configure_file(
|
configure_file(
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in"
|
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in"
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
|
"${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.
|
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).
|
The build definition for the [Meson](https://mesonbuild.com) build system.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### `Package.swift`
|
### `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
|
# 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/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)
|
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 if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources
|
||||||
check-amalgamation:
|
check-amalgamation:
|
||||||
@mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~
|
@mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~
|
||||||
@@ -185,7 +181,7 @@ json.tar.xz:
|
|||||||
# We use `-X` to make the resulting ZIP file reproducible, see
|
# We use `-X` to make the resulting ZIP file reproducible, see
|
||||||
# <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>.
|
# <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>.
|
||||||
include.zip: BUILD.bazel
|
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.
|
# Create the files for a release and add signatures and hashes.
|
||||||
release: include.zip json.tar.xz
|
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@
|
Name: @PROJECT_NAME@
|
||||||
Description: JSON for Modern C++
|
Description: JSON for Modern C++
|
||||||
Version: @PROJECT_VERSION@
|
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
|
- 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
|
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)
|
[`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
|
## Complexity
|
||||||
|
|
||||||
@@ -120,3 +122,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
|
- 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
|
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
|
- 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
|
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)
|
[`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
|
## 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.
|
- 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.
|
- 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.
|
- `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
|
- 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
|
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
|
[`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
|
- 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
|
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)
|
[`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
|
## 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
|
- 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
|
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 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)"`
|
`"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
|
- 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`
|
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
|
## 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;
|
- 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
|
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||||
`number_unsigned_t`.
|
`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
|
- 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
|
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)
|
[`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
|
## 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
|
- 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
|
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 UBJSON.
|
||||||
@@ -21,17 +21,18 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
|
|
||||||
| Compiler | Architecture | Operating System | CI |
|
| 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 | 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.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16.2 | 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.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.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.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.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 3.5.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 |
|
| 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 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 14.2.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 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 |
|
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
||||||
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | 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 |
|
| 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.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
|
## 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.
|
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.
|
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
|
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
|
which case a [pkg-config](pkg-config.md) file is installed. To use it, have your build system require the
|
||||||
the `nlohmann_json` pkg-config dependency, or use [`find_package(nlohmann_json)`](cmake.md#external) in CMake. In Meson,
|
`nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the
|
||||||
it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a
|
[`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than
|
||||||
subproject fallback, rather than using the subproject directly.
|
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.
|
|
||||||
|
|
||||||
??? example "Example: Wrap"
|
??? example "Example: Wrap"
|
||||||
|
|
||||||
|
|||||||
@@ -2021,6 +2021,39 @@ scan_number_done:
|
|||||||
// read the next character and ignore whitespace
|
// read the next character and ignore whitespace
|
||||||
skip_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
|
// ignore comments
|
||||||
while (ignore_comments && current == '/')
|
while (ignore_comments && current == '/')
|
||||||
{
|
{
|
||||||
@@ -2100,7 +2133,6 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// 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(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -423,7 +423,7 @@ class parser
|
|||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// 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
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -463,7 +463,7 @@ class parser
|
|||||||
|
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of object (ignore_trailing_commas = true)
|
// 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();
|
get_token();
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// 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(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -528,6 +528,13 @@ class parser
|
|||||||
return last_token = m_lexer.scan();
|
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 exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
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
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.317 if @a j is not an object
|
@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)
|
void write_bson(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -158,6 +159,7 @@ class binary_writer
|
|||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_cbor(const BasicJsonType& j)
|
void write_cbor(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -322,7 +324,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "CBOR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,6 +384,7 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@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)
|
void write_msgpack(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -655,7 +658,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
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
|
@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
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
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,
|
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||||
const bool use_type, const bool add_prefix = true,
|
const bool use_type, const bool add_prefix = true,
|
||||||
@@ -901,7 +905,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,6 +925,15 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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 //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -1172,6 +1185,7 @@ class binary_writer
|
|||||||
into a byte, before anything is written
|
into a byte, before anything is written
|
||||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||||
anything is written
|
anything is written
|
||||||
|
@throw type_error.321 if @a j is discarded
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -1198,10 +1212,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return 0ul;
|
return 0ul;
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return 0ul;
|
return 0ul;
|
||||||
@@ -1238,10 +1254,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return;
|
return;
|
||||||
@@ -1308,6 +1326,8 @@ class binary_writer
|
|||||||
byte, before anything is written
|
byte, before anything is written
|
||||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||||
before anything is written
|
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)
|
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)
|
/// constructor for rvalue binary arrays (internal type)
|
||||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
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 (
|
if (
|
||||||
(t == value_t::object && object == nullptr) ||
|
(t == value_t::object && object == nullptr) ||
|
||||||
(t == value_t::array && array == nullptr) ||
|
(t == value_t::array && array == nullptr)
|
||||||
(t == value_t::string && string == nullptr) ||
|
|
||||||
(t == value_t::binary && binary == nullptr)
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// not initialized (e.g., due to exception in the ctor)
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t == value_t::array || t == value_t::object)
|
|
||||||
{
|
|
||||||
// flatten the current json_value to a heap-allocated stack
|
|
||||||
std::vector<basic_json> stack;
|
|
||||||
|
|
||||||
// move the top-level items to stack
|
// Destroy the tree without recursing per nesting level and
|
||||||
if (t == value_t::array)
|
// 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)
|
||||||
{
|
{
|
||||||
stack.reserve(array->size());
|
if (has_no_children(cur))
|
||||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
{
|
||||||
|
if (prev.m_data.m_type == value_t::null)
|
||||||
|
{
|
||||||
|
free_container(cur);
|
||||||
|
return; // back at the top with nothing left to do
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// 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))
|
||||||
{
|
{
|
||||||
stack.reserve(object->size());
|
// scalar, or already-empty array/object
|
||||||
for (auto&& it : *object)
|
pop_walk_child(cur);
|
||||||
{
|
continue;
|
||||||
stack.push_back(std::move(it.second));
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!stack.empty())
|
void destroy(value_t t)
|
||||||
{
|
{
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's now safe that current_item gets destructed
|
|
||||||
// since it doesn't have any children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (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:
|
case value_t::string:
|
||||||
{
|
destroy_string();
|
||||||
AllocatorType<string_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
destroy_binary();
|
||||||
AllocatorType<binary_t> alloc;
|
break;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
destroy_container(t);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
@@ -714,11 +853,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
+5
-95
@@ -2,114 +2,24 @@ project('nlohmann_json',
|
|||||||
'cpp',
|
'cpp',
|
||||||
version : '3.12.0',
|
version : '3.12.0',
|
||||||
license : 'MIT',
|
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(
|
nlohmann_json_dep = declare_dependency(
|
||||||
compile_args: cpp_args,
|
include_directories: include_directories('single_include')
|
||||||
include_directories: include_directories(incdir)
|
|
||||||
)
|
)
|
||||||
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(
|
nlohmann_json_multiple_headers = declare_dependency(
|
||||||
compile_args: cpp_args,
|
|
||||||
include_directories: include_directories('include')
|
include_directories: include_directories('include')
|
||||||
)
|
)
|
||||||
|
|
||||||
if not meson.is_subproject()
|
if not meson.is_subproject()
|
||||||
install_subdir(
|
install_headers('single_include/nlohmann/json.hpp', subdir: 'nlohmann')
|
||||||
incdir / 'nlohmann',
|
install_headers('single_include/nlohmann/json_fwd.hpp', subdir: 'nlohmann')
|
||||||
install_dir: get_option('includedir'),
|
install_headers('single_include/nlohmann/json_literals.hpp', subdir: 'nlohmann')
|
||||||
install_tag: 'devel',
|
|
||||||
)
|
|
||||||
|
|
||||||
pkgc = import('pkgconfig')
|
pkgc = import('pkgconfig')
|
||||||
pkgc.generate(name: 'nlohmann_json',
|
pkgc.generate(name: 'nlohmann_json',
|
||||||
version: meson.project_version(),
|
version: meson.project_version(),
|
||||||
description: 'JSON for Modern C++',
|
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
|
|
||||||
endif
|
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
|
// read the next character and ignore whitespace
|
||||||
skip_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
|
// ignore comments
|
||||||
while (ignore_comments && current == '/')
|
while (ignore_comments && current == '/')
|
||||||
{
|
{
|
||||||
@@ -12392,7 +12425,6 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
@@ -18422,7 +18454,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// 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(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -18585,7 +18617,7 @@ class parser
|
|||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// 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
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -18625,7 +18657,7 @@ class parser
|
|||||||
|
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of object (ignore_trailing_commas = true)
|
// 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();
|
get_token();
|
||||||
|
|
||||||
@@ -18646,7 +18678,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// 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(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -18690,6 +18722,13 @@ class parser
|
|||||||
return last_token = m_lexer.scan();
|
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 exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
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
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.317 if @a j is not an object
|
@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)
|
void write_bson(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -21618,6 +21658,7 @@ class binary_writer
|
|||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_cbor(const BasicJsonType& j)
|
void write_cbor(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -21782,7 +21823,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "CBOR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21842,6 +21883,7 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@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)
|
void write_msgpack(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -22115,7 +22157,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
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
|
@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
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
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,
|
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||||
const bool use_type, const bool add_prefix = true,
|
const bool use_type, const bool add_prefix = true,
|
||||||
@@ -22361,7 +22404,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22381,6 +22424,15 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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 //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -22632,6 +22684,7 @@ class binary_writer
|
|||||||
into a byte, before anything is written
|
into a byte, before anything is written
|
||||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||||
anything is written
|
anything is written
|
||||||
|
@throw type_error.321 if @a j is discarded
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -22658,10 +22711,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return 0ul;
|
return 0ul;
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return 0ul;
|
return 0ul;
|
||||||
@@ -22698,10 +22753,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return;
|
return;
|
||||||
@@ -22768,6 +22825,8 @@ class binary_writer
|
|||||||
byte, before anything is written
|
byte, before anything is written
|
||||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||||
before anything is written
|
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)
|
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)
|
/// constructor for rvalue binary arrays (internal type)
|
||||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
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 (
|
if (
|
||||||
(t == value_t::object && object == nullptr) ||
|
(t == value_t::object && object == nullptr) ||
|
||||||
(t == value_t::array && array == nullptr) ||
|
(t == value_t::array && array == nullptr)
|
||||||
(t == value_t::string && string == nullptr) ||
|
|
||||||
(t == value_t::binary && binary == nullptr)
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// not initialized (e.g., due to exception in the ctor)
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t == value_t::array || t == value_t::object)
|
|
||||||
{
|
|
||||||
// flatten the current json_value to a heap-allocated stack
|
|
||||||
std::vector<basic_json> stack;
|
|
||||||
|
|
||||||
// move the top-level items to stack
|
// Destroy the tree without recursing per nesting level and
|
||||||
if (t == value_t::array)
|
// 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)
|
||||||
{
|
{
|
||||||
stack.reserve(array->size());
|
if (has_no_children(cur))
|
||||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
{
|
||||||
|
if (prev.m_data.m_type == value_t::null)
|
||||||
|
{
|
||||||
|
free_container(cur);
|
||||||
|
return; // back at the top with nothing left to do
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// 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))
|
||||||
{
|
{
|
||||||
stack.reserve(object->size());
|
// scalar, or already-empty array/object
|
||||||
for (auto&& it : *object)
|
pop_walk_child(cur);
|
||||||
{
|
continue;
|
||||||
stack.push_back(std::move(it.second));
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!stack.empty())
|
void destroy(value_t t)
|
||||||
{
|
{
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's now safe that current_item gets destructed
|
|
||||||
// since it doesn't have any children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (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:
|
case value_t::string:
|
||||||
{
|
destroy_string();
|
||||||
AllocatorType<string_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
destroy_binary();
|
||||||
AllocatorType<binary_t> alloc;
|
break;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
destroy_container(t);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
@@ -27894,11 +28092,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -607,3 +607,88 @@ TEST_CASE("bad my_allocator::construct")
|
|||||||
j["test"].push_back("should not leak");
|
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")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to BJData
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_bjdata(j);
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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")
|
SECTION("null")
|
||||||
|
|||||||
@@ -149,6 +149,54 @@ TEST_CASE("BSON")
|
|||||||
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
|
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&);
|
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")
|
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
|
||||||
|
|||||||
+42
-3
@@ -38,10 +38,49 @@ TEST_CASE("CBOR")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to CBOR
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_cbor(j);
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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")
|
SECTION("NaN")
|
||||||
|
|||||||
@@ -2317,6 +2317,58 @@ TEST_CASE("parser class")
|
|||||||
#endif
|
#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
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
// Macro for all test cases for start_pos and end_pos
|
// Macro for all test cases for start_pos and end_pos
|
||||||
#define SETUP_TESTCASES() \
|
#define SETUP_TESTCASES() \
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#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>;
|
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
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("object type whose erase() returns void")
|
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")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to MessagePack
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_msgpack(j);
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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")
|
SECTION("null")
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <new>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#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>;
|
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
|
// 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());
|
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
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -35,10 +35,59 @@ TEST_CASE("UBJSON")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to UBJSON
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_ubjson(j);
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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")
|
SECTION("null")
|
||||||
@@ -2099,9 +2148,14 @@ TEST_CASE("UBJSON")
|
|||||||
|
|
||||||
SECTION("discarded")
|
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};
|
json const j = {json::value_t::discarded, json::value_t::discarded};
|
||||||
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
|
#if JSON_DIAGNOSTICS
|
||||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
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