Compare commits
5
Commits
23d3b373e1
...
5ecb704f6b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ecb704f6b | ||
|
|
0490778fc3 | ||
|
|
0a365865f9 | ||
|
|
5379e04ce4 | ||
|
|
8a142bc436 |
No files matched your search
@@ -16,6 +16,9 @@ only_commits:
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
|
||||
# only build the C++17 variant of each test, split into two jobs each to
|
||||
# stay below AppVeyor's 60-minute limit per job.
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
configuration: Debug
|
||||
platform: x86
|
||||
@@ -34,7 +37,13 @@ environment:
|
||||
configuration: Release
|
||||
platform: x86
|
||||
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
|
||||
CMAKE_OPTIONS: ""
|
||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
|
||||
GENERATOR: Visual Studio 15 2017
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
configuration: Release
|
||||
platform: x86
|
||||
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
|
||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
|
||||
GENERATOR: Visual Studio 15 2017
|
||||
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
@@ -55,7 +64,13 @@ environment:
|
||||
configuration: Release
|
||||
platform: x64
|
||||
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
|
||||
CMAKE_OPTIONS: ""
|
||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
|
||||
GENERATOR: Visual Studio 15 2017
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
configuration: Release
|
||||
platform: x64
|
||||
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
|
||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
|
||||
GENERATOR: Visual Studio 15 2017
|
||||
|
||||
init:
|
||||
@@ -66,7 +81,7 @@ install:
|
||||
- if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32
|
||||
|
||||
before_build:
|
||||
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On "%CMAKE_OPTIONS%"
|
||||
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On %CMAKE_OPTIONS%
|
||||
|
||||
build_script:
|
||||
- cmake --build . --config "%configuration%" --parallel 2
|
||||
|
||||
@@ -17,11 +17,11 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
macos-14:
|
||||
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
@@ -36,11 +36,11 @@ jobs:
|
||||
- name: Test
|
||||
run: cd build ; ctest -j 10 --output-on-failure
|
||||
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
macos-26:
|
||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
||||
xcode: ['26.4.1', '26.5', '26.6']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||
container: gcc:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
+1
-1
@@ -376,7 +376,7 @@ add_custom_target(ci_test_coverage
|
||||
# Sanitizers.
|
||||
###############################################################################
|
||||
|
||||
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base")
|
||||
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base -fsanitize-ignorelist=${PROJECT_SOURCE_DIR}/cmake/clang_sanitizer_ignorelist.txt")
|
||||
|
||||
add_custom_target(ci_test_clang_sanitizer
|
||||
COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Sanitizer ignore list for ci_test_clang_sanitizer (-fsanitize-ignorelist).
|
||||
#
|
||||
# libstdc++ 14's <format> declares `_Scanner(basic_string_view<_CharT>, size_t __nargs = -1)`, so every std::format
|
||||
# call converts -1 to size_t, which -fsanitize=integer reports as implicit-integer-sign-change. This is
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119429, not a bug in this library. Only that check and only <format> are
|
||||
# excluded, so implicit sign changes in the library and the tests are still reported.
|
||||
[implicit-integer-sign-change]
|
||||
src:*/include/c++/*/format
|
||||
@@ -250,6 +250,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html');
|
||||
|
||||
@@ -68,6 +68,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to BJData"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -119,4 +121,6 @@ Linear in the size of the JSON value `j`.
|
||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
@@ -58,6 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded
|
||||
(the top-level value itself is covered by `type_error.317` above, since it must be an object); example:
|
||||
`"cannot serialize discarded value to BSON"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -110,6 +113,8 @@ pass before anything is written.
|
||||
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
||||
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
||||
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
||||
- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently
|
||||
skipped, producing a document whose declared size did not match what was actually written.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||
|
||||
@@ -49,6 +49,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to CBOR"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -86,3 +88,5 @@ Linear in the size of the JSON value `j`.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid CBOR.
|
||||
@@ -54,6 +54,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict`
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to MessagePack"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -108,3 +110,5 @@ Linear in the size of the JSON value `j`.
|
||||
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
||||
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||
`number_unsigned_t`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid MessagePack.
|
||||
@@ -61,6 +61,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to UBJSON"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -112,3 +114,5 @@ Linear in the size of the JSON value `j`.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid UBJSON.
|
||||
@@ -69,7 +69,7 @@ The default value is `0` (disabled — existing behavior is preserved).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Default behavior (macro not defined)"
|
||||
??? example "Example: default behavior (macro not defined)"
|
||||
|
||||
Without the macro, a map with enum keys is stored as an array of pairs:
|
||||
|
||||
@@ -96,7 +96,7 @@ The default value is `0` (disabled — existing behavior is preserved).
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Objects for enum-keyed maps (macro defined to 1)"
|
||||
??? example "Example: objects for enum-keyed maps (macro defined to 1)"
|
||||
|
||||
With the macro, the same map is stored as an object:
|
||||
|
||||
|
||||
@@ -21,17 +21,18 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
|
||||
| Compiler | Architecture | Operating System | CI |
|
||||
|----------------------------------------------|--------------|-----------------------------------|-----------|
|
||||
| AppleClang 15.0.0.15000040; Xcode 15.0.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000100; Xcode 15.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000100; Xcode 15.2 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000309; Xcode 15.3 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000309; Xcode 15.4 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000404; Xcode 26.1.1 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000603; Xcode 26.2 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000604; Xcode 26.3 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 21.0.0.21000099; Xcode 26.4.1 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| AppleClang 21.0.0.21000101; Xcode 26.5 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| AppleClang 21.0.0.21000101; Xcode 26.6 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
@@ -89,7 +90,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
||||
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
||||
|
||||
@@ -804,6 +804,20 @@ does not list an enumerator and it is therefore converted like the first listed
|
||||
[json.exception.type_error.318] duplicate object key 'red'
|
||||
```
|
||||
|
||||
### json.exception.type_error.321
|
||||
|
||||
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
|
||||
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
|
||||
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
|
||||
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback:
|
||||
```
|
||||
[json.exception.type_error.321] cannot serialize discarded value to CBOR
|
||||
```
|
||||
|
||||
## Out of range
|
||||
|
||||
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
||||
|
||||
@@ -172,7 +172,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e)
|
||||
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
|
||||
value_type val;
|
||||
get_arithmetic_value(j, val);
|
||||
e = static_cast<EnumType>(static_cast<underlying_type>(val));
|
||||
e = static_cast<EnumType>(bool_aware_static_cast<underlying_type>(val));
|
||||
}
|
||||
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
||||
|
||||
|
||||
@@ -287,9 +287,20 @@ struct external_constructor<value_t::object>
|
||||
/////////////
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// whether storing the value of a std::optional<T> cannot throw; MSVC 2017
|
||||
// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so
|
||||
// the exception would call std::terminate (#5642)
|
||||
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_nothrow_optional_to_json = std::false_type;
|
||||
#else
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_nothrow_optional_to_json = std::is_nothrow_assignable<BasicJsonType&, const T&>;
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename T,
|
||||
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
|
||||
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_nothrow_assignable<BasicJsonType&, const T&>::value)
|
||||
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(is_nothrow_optional_to_json<BasicJsonType, T>::value)
|
||||
{
|
||||
if (opt.has_value())
|
||||
{
|
||||
@@ -365,7 +376,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept
|
||||
{
|
||||
using underlying_type = typename std::underlying_type<EnumType>::type;
|
||||
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
|
||||
external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));
|
||||
external_constructor<integral_value_t>::construct(j, bool_aware_static_cast<underlying_type>(e));
|
||||
}
|
||||
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
||||
|
||||
|
||||
@@ -662,10 +662,11 @@ class binary_reader
|
||||
}
|
||||
|
||||
// the value is -1 - number, which fits into number_integer_t
|
||||
// whenever number does
|
||||
// whenever number does; the outer cast undoes the integral promotion
|
||||
// for number_integer_t types narrower than int
|
||||
if (JSON_HEDLEY_LIKELY(value_in_range_of<number_integer_t>(number)))
|
||||
{
|
||||
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
|
||||
return sax->number_integer(conditional_static_cast<number_integer_t>(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number)));
|
||||
}
|
||||
|
||||
// like the lexer does for JSON text, store a value too small for
|
||||
@@ -4003,7 +4004,8 @@ class binary_reader
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(number));
|
||||
}
|
||||
return emit_float(number);
|
||||
// std::isfinite has no integer overloads in MSVC's <cmath>
|
||||
return emit_float(static_cast<long double>(number));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4026,7 +4028,8 @@ class binary_reader
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(number));
|
||||
}
|
||||
return emit_float(number);
|
||||
// std::isfinite has no integer overloads in MSVC's <cmath>
|
||||
return emit_float(static_cast<long double>(number));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4037,7 +4040,8 @@ class binary_reader
|
||||
and NaN in the input are passed on unchanged. Integers only overflow if
|
||||
number_float_t cannot represent 2^64, e.g., a half-precision type.
|
||||
|
||||
@tparam NumberType a floating-point or integer type
|
||||
@tparam NumberType a floating-point type (emit_signed and emit_unsigned
|
||||
convert integers to long double first)
|
||||
@param[in] number the number
|
||||
@return whether the SAX parser accepted the value
|
||||
|
||||
|
||||
@@ -2021,6 +2021,39 @@ scan_number_done:
|
||||
// read the next character and ignore whitespace
|
||||
skip_whitespace();
|
||||
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan the next token when the caller expects a separator (':' or
|
||||
',') most of the time
|
||||
|
||||
After an object key the next token is almost always ':', after a value
|
||||
inside an object or array almost always ','. Testing for that character
|
||||
first is a compare and a well-predicted branch, where the switch in
|
||||
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||
goes through the switch, so the result is the same as scan()'s.
|
||||
|
||||
May only be called after scan() has run once (the BOM check is skipped).
|
||||
*/
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
{
|
||||
return expected_type;
|
||||
}
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
private:
|
||||
/// the part of scan() after the leading whitespace: skip comments and
|
||||
/// scan the token that starts with current
|
||||
token_type scan_after_whitespace()
|
||||
{
|
||||
// ignore comments
|
||||
while (ignore_comments && current == '/')
|
||||
{
|
||||
@@ -2100,7 +2133,6 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -423,7 +423,7 @@ class parser
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
// parse a new value
|
||||
get_token();
|
||||
@@ -463,7 +463,7 @@ class parser
|
||||
|
||||
// comma -> next value
|
||||
// or end of object (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
get_token();
|
||||
|
||||
@@ -484,7 +484,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -528,6 +528,13 @@ class parser
|
||||
return last_token = m_lexer.scan();
|
||||
}
|
||||
|
||||
/// get next token from lexer; true if it is the separator @a expected_type
|
||||
/// (name_separator or value_separator), which it usually is
|
||||
bool get_token_expecting(token_type expected_type)
|
||||
{
|
||||
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
|
||||
@@ -906,6 +906,21 @@ T conditional_static_cast(U value)
|
||||
return value;
|
||||
}
|
||||
|
||||
// like conditional_static_cast, but converts to bool by comparing with zero,
|
||||
// because MSVC 2015 warns about any conversion to bool (C4800), even with an
|
||||
// explicit cast; used for enums whose underlying type is bool
|
||||
template < typename T, typename U, enable_if_t < !std::is_same<T, bool>::value, int > = 0 >
|
||||
T bool_aware_static_cast(U value)
|
||||
{
|
||||
return conditional_static_cast<T>(value);
|
||||
}
|
||||
|
||||
template<typename T, typename U, enable_if_t<std::is_same<T, bool>::value, int> = 0>
|
||||
bool bool_aware_static_cast(U value)
|
||||
{
|
||||
return value != U();
|
||||
}
|
||||
|
||||
template<typename... Types>
|
||||
using all_integral = conjunction<std::is_integral<Types>...>;
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ class binary_writer
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.317 if @a j is not an object
|
||||
@throw type_error.321 if a value nested in @a j is discarded
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -158,6 +159,7 @@ class binary_writer
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -322,7 +324,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "CBOR");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +384,7 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_msgpack(const BasicJsonType& j)
|
||||
{
|
||||
@@ -655,7 +658,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,6 +671,7 @@ class binary_writer
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -901,7 +905,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,6 +925,15 @@ class binary_writer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
{
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -1172,6 +1185,7 @@ class binary_writer
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
*/
|
||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -1198,10 +1212,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return 0ul;
|
||||
@@ -1238,10 +1254,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return;
|
||||
@@ -1308,6 +1326,8 @@ class binary_writer
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
@throw type_error.321 if a value nested in @a document is discarded,
|
||||
before anything is written
|
||||
*/
|
||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
|
||||
+218
-93
@@ -612,100 +612,210 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// constructor for rvalue binary arrays (internal type)
|
||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||
|
||||
void destroy(value_t t)
|
||||
private:
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static basic_json& last_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 v.m_data.m_value.object->rbegin()->second;
|
||||
}
|
||||
|
||||
// removes the last child of 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_last_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);
|
||||
// erase() needs a forward iterator, so std::prev(end()) is
|
||||
// used here rather than rbegin() (see last_child() above)
|
||||
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
||||
}
|
||||
}
|
||||
|
||||
// deallocates the (already empty) array/object held by v; this is
|
||||
// the same allocator-based free the old recursive implementation
|
||||
// used, just factored out so every level of the walk in destroy()
|
||||
// can share it
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
void destroy_container(value_t t) noexcept
|
||||
{
|
||||
if (
|
||||
(t == value_t::object && object == nullptr) ||
|
||||
(t == value_t::array && array == nullptr) ||
|
||||
(t == value_t::string && string == nullptr) ||
|
||||
(t == value_t::binary && binary == nullptr)
|
||||
(t == value_t::array && array == nullptr)
|
||||
)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
if (t == value_t::array || t == value_t::object)
|
||||
|
||||
// Destroy the tree without recursing per nesting level and
|
||||
// without any heap allocation: a heap-allocated flattening
|
||||
// stack (the previous implementation) can itself throw
|
||||
// bad_alloc, which would escape this noexcept destructor and
|
||||
// terminate the program (#5135).
|
||||
//
|
||||
// Instead, walk down the "last child" chain, 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 last child slot doubles as storage for that
|
||||
// parent's own parent link while we are below it, so no
|
||||
// extra memory is needed. We only ever remove a child once
|
||||
// it is a scalar or an empty array/object, which neither
|
||||
// allocates nor recurses more than one level deep.
|
||||
//
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
while (true)
|
||||
{
|
||||
// flatten the current json_value to a heap-allocated stack
|
||||
std::vector<basic_json> stack;
|
||||
|
||||
// move the top-level items to stack
|
||||
if (t == value_t::array)
|
||||
if (has_no_children(cur))
|
||||
{
|
||||
stack.reserve(array->size());
|
||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.reserve(object->size());
|
||||
for (auto&& it : *object)
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
// move the last item to a local variable to be processed
|
||||
basic_json current_item(std::move(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
// if current_item is array/object, move
|
||||
// its children to the stack to be processed later
|
||||
if (current_item.is_array())
|
||||
{
|
||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||
|
||||
current_item.m_data.m_value.array->clear();
|
||||
}
|
||||
else if (current_item.is_object())
|
||||
{
|
||||
for (auto&& it : *current_item.m_data.m_value.object)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
|
||||
current_item.m_data.m_value.object->clear();
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
}
|
||||
|
||||
// it's now safe that current_item gets destructed
|
||||
// since it doesn't have any children
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// last slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
basic_json gp;
|
||||
take(gp, last_child(prev));
|
||||
pop_last_child(prev);
|
||||
|
||||
free_container(cur);
|
||||
|
||||
take(cur, prev);
|
||||
take(prev, gp);
|
||||
continue;
|
||||
}
|
||||
|
||||
basic_json& cur_last_ref = last_child(cur);
|
||||
|
||||
if (has_no_children(cur_last_ref))
|
||||
{
|
||||
// scalar, or already-empty array/object
|
||||
pop_last_child(cur);
|
||||
continue;
|
||||
}
|
||||
|
||||
// descend into the non-empty last child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
basic_json tmp;
|
||||
take(tmp, cur_last_ref);
|
||||
take(cur_last_ref, prev);
|
||||
take(prev, cur);
|
||||
take(cur, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(value_t t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
destroy_string();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
destroy_binary();
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
destroy_container(t);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
@@ -714,9 +824,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -5098,7 +5206,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType>
|
||||
requires std::is_scalar_v<ScalarType>
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) <= rhs;
|
||||
}
|
||||
@@ -5107,7 +5215,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType>
|
||||
requires std::is_scalar_v<ScalarType>
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) >= rhs;
|
||||
}
|
||||
@@ -5128,11 +5236,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#endif
|
||||
}
|
||||
|
||||
// The friend comparisons with a scalar name the JSON type via decltype of
|
||||
// their parameter in noexcept, because older MSVC versions do not see the
|
||||
// class scope there: MSVC 2015 and 2017 take basic_json as the template,
|
||||
// and MSVC 2019 16.0 rejects member types and template parameters.
|
||||
|
||||
/// @brief comparison: equal
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs == basic_json(rhs);
|
||||
}
|
||||
@@ -5141,7 +5254,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) == rhs;
|
||||
}
|
||||
@@ -5157,7 +5270,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs != basic_json(rhs);
|
||||
}
|
||||
@@ -5166,7 +5279,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) != rhs;
|
||||
}
|
||||
@@ -5186,7 +5299,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs < basic_json(rhs);
|
||||
}
|
||||
@@ -5195,7 +5308,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) < rhs;
|
||||
}
|
||||
@@ -5215,7 +5328,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs <= basic_json(rhs);
|
||||
}
|
||||
@@ -5224,7 +5337,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) <= rhs;
|
||||
}
|
||||
@@ -5245,7 +5358,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs > basic_json(rhs);
|
||||
}
|
||||
@@ -5254,7 +5367,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) > rhs;
|
||||
}
|
||||
@@ -5274,7 +5387,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs >= basic_json(rhs);
|
||||
}
|
||||
@@ -5283,7 +5396,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) >= rhs;
|
||||
}
|
||||
@@ -5319,6 +5432,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return o;
|
||||
}
|
||||
|
||||
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
// the deleted version is a function template after the class, because
|
||||
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
|
||||
/// @brief serialize to stream
|
||||
/// @sa https://json.nlohmann.me/api/operator_ltlt/
|
||||
/// @deprecated This function is deprecated since 3.0.0 and will be removed in
|
||||
@@ -5327,9 +5443,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// replace calls like `j >> o;` with `o << j;`.
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&))
|
||||
friend std::ostream& operator>>(const basic_json& j, std::ostream& o)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return o << j;
|
||||
}
|
||||
@@ -5507,6 +5620,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
#ifndef JSON_NO_IO
|
||||
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
// the deleted version is a function template after the class, because
|
||||
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
|
||||
/// @brief deserialize from stream
|
||||
/// @sa https://json.nlohmann.me/api/operator_gtgt/
|
||||
/// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in
|
||||
@@ -5515,9 +5631,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// replace calls like `j << i;` with `i >> j;`.
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&))
|
||||
friend std::istream& operator<<(basic_json& j, std::istream& i)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return operator>>(i, j);
|
||||
}
|
||||
@@ -7178,6 +7291,18 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j)
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO)
|
||||
/// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&))
|
||||
/// @sa https://json.nlohmann.me/api/operator_ltlt/
|
||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete;
|
||||
|
||||
/// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&))
|
||||
/// @sa https://json.nlohmann.me/api/operator_gtgt/
|
||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete;
|
||||
#endif
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
///////////////////////
|
||||
|
||||
+325
-111
@@ -4905,6 +4905,21 @@ T conditional_static_cast(U value)
|
||||
return value;
|
||||
}
|
||||
|
||||
// like conditional_static_cast, but converts to bool by comparing with zero,
|
||||
// because MSVC 2015 warns about any conversion to bool (C4800), even with an
|
||||
// explicit cast; used for enums whose underlying type is bool
|
||||
template < typename T, typename U, enable_if_t < !std::is_same<T, bool>::value, int > = 0 >
|
||||
T bool_aware_static_cast(U value)
|
||||
{
|
||||
return conditional_static_cast<T>(value);
|
||||
}
|
||||
|
||||
template<typename T, typename U, enable_if_t<std::is_same<T, bool>::value, int> = 0>
|
||||
bool bool_aware_static_cast(U value)
|
||||
{
|
||||
return value != U();
|
||||
}
|
||||
|
||||
template<typename... Types>
|
||||
using all_integral = conjunction<std::is_integral<Types>...>;
|
||||
|
||||
@@ -5700,7 +5715,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e)
|
||||
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
|
||||
value_type val;
|
||||
get_arithmetic_value(j, val);
|
||||
e = static_cast<EnumType>(static_cast<underlying_type>(val));
|
||||
e = static_cast<EnumType>(bool_aware_static_cast<underlying_type>(val));
|
||||
}
|
||||
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
||||
|
||||
@@ -7066,9 +7081,20 @@ struct external_constructor<value_t::object>
|
||||
/////////////
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// whether storing the value of a std::optional<T> cannot throw; MSVC 2017
|
||||
// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so
|
||||
// the exception would call std::terminate (#5642)
|
||||
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_nothrow_optional_to_json = std::false_type;
|
||||
#else
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_nothrow_optional_to_json = std::is_nothrow_assignable<BasicJsonType&, const T&>;
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename T,
|
||||
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
|
||||
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_nothrow_assignable<BasicJsonType&, const T&>::value)
|
||||
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(is_nothrow_optional_to_json<BasicJsonType, T>::value)
|
||||
{
|
||||
if (opt.has_value())
|
||||
{
|
||||
@@ -7144,7 +7170,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept
|
||||
{
|
||||
using underlying_type = typename std::underlying_type<EnumType>::type;
|
||||
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
|
||||
external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));
|
||||
external_constructor<integral_value_t>::construct(j, bool_aware_static_cast<underlying_type>(e));
|
||||
}
|
||||
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
||||
|
||||
@@ -12287,6 +12313,39 @@ scan_number_done:
|
||||
// read the next character and ignore whitespace
|
||||
skip_whitespace();
|
||||
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan the next token when the caller expects a separator (':' or
|
||||
',') most of the time
|
||||
|
||||
After an object key the next token is almost always ':', after a value
|
||||
inside an object or array almost always ','. Testing for that character
|
||||
first is a compare and a well-predicted branch, where the switch in
|
||||
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||
goes through the switch, so the result is the same as scan()'s.
|
||||
|
||||
May only be called after scan() has run once (the BOM check is skipped).
|
||||
*/
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
{
|
||||
return expected_type;
|
||||
}
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
private:
|
||||
/// the part of scan() after the leading whitespace: skip comments and
|
||||
/// scan the token that starts with current
|
||||
token_type scan_after_whitespace()
|
||||
{
|
||||
// ignore comments
|
||||
while (ignore_comments && current == '/')
|
||||
{
|
||||
@@ -12366,7 +12425,6 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
|
||||
@@ -14353,10 +14411,11 @@ class binary_reader
|
||||
}
|
||||
|
||||
// the value is -1 - number, which fits into number_integer_t
|
||||
// whenever number does
|
||||
// whenever number does; the outer cast undoes the integral promotion
|
||||
// for number_integer_t types narrower than int
|
||||
if (JSON_HEDLEY_LIKELY(value_in_range_of<number_integer_t>(number)))
|
||||
{
|
||||
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
|
||||
return sax->number_integer(conditional_static_cast<number_integer_t>(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number)));
|
||||
}
|
||||
|
||||
// like the lexer does for JSON text, store a value too small for
|
||||
@@ -17694,7 +17753,8 @@ class binary_reader
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(number));
|
||||
}
|
||||
return emit_float(number);
|
||||
// std::isfinite has no integer overloads in MSVC's <cmath>
|
||||
return emit_float(static_cast<long double>(number));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -17717,7 +17777,8 @@ class binary_reader
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(number));
|
||||
}
|
||||
return emit_float(number);
|
||||
// std::isfinite has no integer overloads in MSVC's <cmath>
|
||||
return emit_float(static_cast<long double>(number));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -17728,7 +17789,8 @@ class binary_reader
|
||||
and NaN in the input are passed on unchanged. Integers only overflow if
|
||||
number_float_t cannot represent 2^64, e.g., a half-precision type.
|
||||
|
||||
@tparam NumberType a floating-point or integer type
|
||||
@tparam NumberType a floating-point type (emit_signed and emit_unsigned
|
||||
convert integers to long double first)
|
||||
@param[in] number the number
|
||||
@return whether the SAX parser accepted the value
|
||||
|
||||
@@ -18392,7 +18454,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -18555,7 +18617,7 @@ class parser
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
// parse a new value
|
||||
get_token();
|
||||
@@ -18595,7 +18657,7 @@ class parser
|
||||
|
||||
// comma -> next value
|
||||
// or end of object (ignore_trailing_commas = true)
|
||||
if (get_token() == token_type::value_separator)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
{
|
||||
get_token();
|
||||
|
||||
@@ -18616,7 +18678,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -18660,6 +18722,13 @@ class parser
|
||||
return last_token = m_lexer.scan();
|
||||
}
|
||||
|
||||
/// get next token from lexer; true if it is the separator @a expected_type
|
||||
/// (name_separator or value_separator), which it usually is
|
||||
bool get_token_expecting(token_type expected_type)
|
||||
{
|
||||
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
@@ -21557,6 +21626,7 @@ class binary_writer
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.317 if @a j is not an object
|
||||
@throw type_error.321 if a value nested in @a j is discarded
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21588,6 +21658,7 @@ class binary_writer
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21752,7 +21823,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "CBOR");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21812,6 +21883,7 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_msgpack(const BasicJsonType& j)
|
||||
{
|
||||
@@ -22085,7 +22157,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22098,6 +22170,7 @@ class binary_writer
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -22331,7 +22404,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22351,6 +22424,15 @@ class binary_writer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
{
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -22602,6 +22684,7 @@ class binary_writer
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
*/
|
||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -22628,10 +22711,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return 0ul;
|
||||
@@ -22668,10 +22753,12 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return;
|
||||
@@ -22738,6 +22825,8 @@ class binary_writer
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
@throw type_error.321 if a value nested in @a document is discarded,
|
||||
before anything is written
|
||||
*/
|
||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
@@ -27762,100 +27851,210 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// constructor for rvalue binary arrays (internal type)
|
||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||
|
||||
void destroy(value_t t)
|
||||
private:
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static basic_json& last_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 v.m_data.m_value.object->rbegin()->second;
|
||||
}
|
||||
|
||||
// removes the last child of 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_last_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);
|
||||
// erase() needs a forward iterator, so std::prev(end()) is
|
||||
// used here rather than rbegin() (see last_child() above)
|
||||
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
||||
}
|
||||
}
|
||||
|
||||
// deallocates the (already empty) array/object held by v; this is
|
||||
// the same allocator-based free the old recursive implementation
|
||||
// used, just factored out so every level of the walk in destroy()
|
||||
// can share it
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
void destroy_container(value_t t) noexcept
|
||||
{
|
||||
if (
|
||||
(t == value_t::object && object == nullptr) ||
|
||||
(t == value_t::array && array == nullptr) ||
|
||||
(t == value_t::string && string == nullptr) ||
|
||||
(t == value_t::binary && binary == nullptr)
|
||||
(t == value_t::array && array == nullptr)
|
||||
)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
if (t == value_t::array || t == value_t::object)
|
||||
|
||||
// Destroy the tree without recursing per nesting level and
|
||||
// without any heap allocation: a heap-allocated flattening
|
||||
// stack (the previous implementation) can itself throw
|
||||
// bad_alloc, which would escape this noexcept destructor and
|
||||
// terminate the program (#5135).
|
||||
//
|
||||
// Instead, walk down the "last child" chain, 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 last child slot doubles as storage for that
|
||||
// parent's own parent link while we are below it, so no
|
||||
// extra memory is needed. We only ever remove a child once
|
||||
// it is a scalar or an empty array/object, which neither
|
||||
// allocates nor recurses more than one level deep.
|
||||
//
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
while (true)
|
||||
{
|
||||
// flatten the current json_value to a heap-allocated stack
|
||||
std::vector<basic_json> stack;
|
||||
|
||||
// move the top-level items to stack
|
||||
if (t == value_t::array)
|
||||
if (has_no_children(cur))
|
||||
{
|
||||
stack.reserve(array->size());
|
||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.reserve(object->size());
|
||||
for (auto&& it : *object)
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
// move the last item to a local variable to be processed
|
||||
basic_json current_item(std::move(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
// if current_item is array/object, move
|
||||
// its children to the stack to be processed later
|
||||
if (current_item.is_array())
|
||||
{
|
||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||
|
||||
current_item.m_data.m_value.array->clear();
|
||||
}
|
||||
else if (current_item.is_object())
|
||||
{
|
||||
for (auto&& it : *current_item.m_data.m_value.object)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
|
||||
current_item.m_data.m_value.object->clear();
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
}
|
||||
|
||||
// it's now safe that current_item gets destructed
|
||||
// since it doesn't have any children
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// last slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
basic_json gp;
|
||||
take(gp, last_child(prev));
|
||||
pop_last_child(prev);
|
||||
|
||||
free_container(cur);
|
||||
|
||||
take(cur, prev);
|
||||
take(prev, gp);
|
||||
continue;
|
||||
}
|
||||
|
||||
basic_json& cur_last_ref = last_child(cur);
|
||||
|
||||
if (has_no_children(cur_last_ref))
|
||||
{
|
||||
// scalar, or already-empty array/object
|
||||
pop_last_child(cur);
|
||||
continue;
|
||||
}
|
||||
|
||||
// descend into the non-empty last child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
basic_json tmp;
|
||||
take(tmp, cur_last_ref);
|
||||
take(cur_last_ref, prev);
|
||||
take(prev, cur);
|
||||
take(cur, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(value_t t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
destroy_string();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
destroy_binary();
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
destroy_container(t);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
@@ -27864,9 +28063,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -32248,7 +32445,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType>
|
||||
requires std::is_scalar_v<ScalarType>
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) <= rhs;
|
||||
}
|
||||
@@ -32257,7 +32454,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType>
|
||||
requires std::is_scalar_v<ScalarType>
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) >= rhs;
|
||||
}
|
||||
@@ -32278,11 +32475,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#endif
|
||||
}
|
||||
|
||||
// The friend comparisons with a scalar name the JSON type via decltype of
|
||||
// their parameter in noexcept, because older MSVC versions do not see the
|
||||
// class scope there: MSVC 2015 and 2017 take basic_json as the template,
|
||||
// and MSVC 2019 16.0 rejects member types and template parameters.
|
||||
|
||||
/// @brief comparison: equal
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs == basic_json(rhs);
|
||||
}
|
||||
@@ -32291,7 +32493,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) == rhs;
|
||||
}
|
||||
@@ -32307,7 +32509,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs != basic_json(rhs);
|
||||
}
|
||||
@@ -32316,7 +32518,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) != rhs;
|
||||
}
|
||||
@@ -32336,7 +32538,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs < basic_json(rhs);
|
||||
}
|
||||
@@ -32345,7 +32547,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) < rhs;
|
||||
}
|
||||
@@ -32365,7 +32567,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs <= basic_json(rhs);
|
||||
}
|
||||
@@ -32374,7 +32576,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) <= rhs;
|
||||
}
|
||||
@@ -32395,7 +32597,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs > basic_json(rhs);
|
||||
}
|
||||
@@ -32404,7 +32606,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) > rhs;
|
||||
}
|
||||
@@ -32424,7 +32626,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
{
|
||||
return lhs >= basic_json(rhs);
|
||||
}
|
||||
@@ -32433,7 +32635,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) >= rhs;
|
||||
}
|
||||
@@ -32469,6 +32671,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return o;
|
||||
}
|
||||
|
||||
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
// the deleted version is a function template after the class, because
|
||||
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
|
||||
/// @brief serialize to stream
|
||||
/// @sa https://json.nlohmann.me/api/operator_ltlt/
|
||||
/// @deprecated This function is deprecated since 3.0.0 and will be removed in
|
||||
@@ -32477,9 +32682,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// replace calls like `j >> o;` with `o << j;`.
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&))
|
||||
friend std::ostream& operator>>(const basic_json& j, std::ostream& o)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return o << j;
|
||||
}
|
||||
@@ -32657,6 +32859,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
#ifndef JSON_NO_IO
|
||||
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
// the deleted version is a function template after the class, because
|
||||
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
|
||||
/// @brief deserialize from stream
|
||||
/// @sa https://json.nlohmann.me/api/operator_gtgt/
|
||||
/// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in
|
||||
@@ -32665,9 +32870,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// replace calls like `j << i;` with `i >> j;`.
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&))
|
||||
friend std::istream& operator<<(basic_json& j, std::istream& i)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return operator>>(i, j);
|
||||
}
|
||||
@@ -34328,6 +34530,18 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j)
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO)
|
||||
/// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&))
|
||||
/// @sa https://json.nlohmann.me/api/operator_ltlt/
|
||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete;
|
||||
|
||||
/// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&))
|
||||
/// @sa https://json.nlohmann.me/api/operator_gtgt/
|
||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete;
|
||||
#endif
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
///////////////////////
|
||||
|
||||
@@ -8,6 +8,7 @@ set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_Te
|
||||
|
||||
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
||||
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
||||
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
|
||||
|
||||
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
||||
if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}")
|
||||
@@ -288,6 +289,33 @@ elseif(NOT json_32bit_test)
|
||||
list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp)
|
||||
endif()
|
||||
|
||||
# with JSON_TestShard=<index>/<count>, keep every <count>-th unit test file,
|
||||
# starting at <index> (the glob is sorted, so the split is stable)
|
||||
set(test_shard_index 0)
|
||||
if(NOT "${JSON_TestShard}" STREQUAL "")
|
||||
if(NOT JSON_TestShard MATCHES "^([0-9]+)/([1-9][0-9]*)$")
|
||||
message(FATAL_ERROR "JSON_TestShard must be <index>/<count>, e.g. 0/2, not '${JSON_TestShard}'.")
|
||||
endif()
|
||||
set(test_shard_index ${CMAKE_MATCH_1})
|
||||
set(test_shard_count ${CMAKE_MATCH_2})
|
||||
if(NOT test_shard_index LESS test_shard_count)
|
||||
message(FATAL_ERROR "JSON_TestShard: the index must be less than the count, not '${JSON_TestShard}'.")
|
||||
endif()
|
||||
list(LENGTH files test_file_count)
|
||||
set(shard_files "")
|
||||
set(file_position 0)
|
||||
foreach(file ${files})
|
||||
math(EXPR file_shard "${file_position} % ${test_shard_count}")
|
||||
if(file_shard EQUAL test_shard_index)
|
||||
list(APPEND shard_files ${file})
|
||||
endif()
|
||||
math(EXPR file_position "${file_position} + 1")
|
||||
endforeach()
|
||||
set(files ${shard_files})
|
||||
list(LENGTH files shard_file_count)
|
||||
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
|
||||
endif()
|
||||
|
||||
foreach(file ${files})
|
||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||
endforeach()
|
||||
@@ -305,6 +333,9 @@ if(json_32bit_test_only)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# the following variants of single test files are only built in the first shard
|
||||
if(test_shard_index EQUAL 0)
|
||||
|
||||
# test legacy comparison of discarded values
|
||||
json_test_set_test_options(test-comparison_legacy
|
||||
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||
@@ -332,6 +363,8 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
# *DO NOT* use json_test_set_test_options() below this line
|
||||
|
||||
#############################################################################
|
||||
|
||||
@@ -521,6 +521,10 @@ struct countdown_allocator : std::allocator<T>
|
||||
|
||||
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
|
||||
{
|
||||
// MSVC 2015's debug STL constructs the containers' debug proxies through
|
||||
// the allocator in noexcept constructors, so a failing construction crashes
|
||||
// the program there instead of throwing std::bad_alloc. Nothing to check.
|
||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||
using countdown_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
@@ -558,6 +562,7 @@ TEST_CASE("converting a deeply nested value from another specialization fails cl
|
||||
}
|
||||
}
|
||||
CHECK(failures > 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace
|
||||
@@ -602,3 +607,88 @@ TEST_CASE("bad my_allocator::construct")
|
||||
j["test"].push_back("should not leak");
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t counting_allocator_allocations = 0;
|
||||
std::size_t counting_allocator_deallocations = 0;
|
||||
|
||||
template<class T>
|
||||
struct counting_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
++counting_allocator_allocations;
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
++counting_allocator_deallocations;
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = counting_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("destructor performs no allocation, only deallocation")
|
||||
{
|
||||
// see https://github.com/nlohmann/json/issues/4842 and
|
||||
// https://github.com/nlohmann/json/issues/5135: destroying nested
|
||||
// arrays/objects used to allocate a temporary stack (first with
|
||||
// std::allocator, later - after #4842 - with the provided allocator).
|
||||
// Since that stack could itself throw bad_alloc from inside the
|
||||
// noexcept destructor (#5135), destroy() no longer allocates anything:
|
||||
// it only ever frees what is already there.
|
||||
using counting_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
counting_allocator>;
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
|
||||
SECTION("mixed tree of empty/non-empty arrays and objects")
|
||||
{
|
||||
auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory)
|
||||
{
|
||||
{"empty_obj", counting_json::object()},
|
||||
{"empty_arr", counting_json::array()},
|
||||
{"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}},
|
||||
{"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})}
|
||||
});
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
}
|
||||
@@ -114,10 +114,59 @@ TEST_CASE("BJData")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to BJData
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_bjdata(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("optimized array of all-discarded elements")
|
||||
{
|
||||
json const j = {discarded, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
|
||||
@@ -149,6 +149,54 @@ TEST_CASE("BSON")
|
||||
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
json const j = json::value_t::discarded;
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is discarded", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container cannot be serialized to BSON")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("in an array that is an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = json::array({1, discarded, 2});
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/a/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in object)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json j;
|
||||
j["outer"] = middle_object;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/outer/x/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
|
||||
|
||||
+42
-3
@@ -38,10 +38,49 @@ TEST_CASE("CBOR")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to CBOR
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_cbor(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("NaN")
|
||||
|
||||
@@ -2317,6 +2317,58 @@ TEST_CASE("parser class")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("comments before separators")
|
||||
{
|
||||
// The parser first checks for the expected ':' or ',' and only then
|
||||
// falls back to the full token switch, which skips comments. A comment
|
||||
// directly before a separator takes that fallback.
|
||||
json _;
|
||||
|
||||
SECTION("ignored")
|
||||
{
|
||||
const std::vector<std::pair<std::string, json>> inputs =
|
||||
{
|
||||
{"{\"a\" /* c */ : 1}", {{"a", 1}}},
|
||||
{"{\"a\" // c\n: 1}", {{"a", 1}}},
|
||||
{R"({"a": 1, "b" /* c */ : 2})", {{"a", 1}, {"b", 2}}},
|
||||
{R"({"a": 1 /* c */ , "b": 2})", {{"a", 1}, {"b", 2}}},
|
||||
{"{\"a\": 1 // c\n, \"b\": 2}", {{"a", 1}, {"b", 2}}},
|
||||
{"[1 /* c */ , 2]", {1, 2}},
|
||||
{"[1 // c\n, 2]", {1, 2}},
|
||||
{"{\"a\" /* c */ /* d */ : [1 // c\n , 2 /**/ ] /**/ , \"b\" : 3}", {{"a", {1, 2}}, {"b", 3}}}
|
||||
};
|
||||
for (const auto& input : inputs)
|
||||
{
|
||||
CAPTURE(input.first)
|
||||
CHECK(json::parse(input.first, nullptr, true, true) == input.second);
|
||||
CHECK(json::accept(input.first, true));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ignored, with trailing commas")
|
||||
{
|
||||
CHECK(json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true, true) == json({1}));
|
||||
CHECK(json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true, true) == json({{"a", 1}}));
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 19: syntax error while parsing object key - unexpected '}'; expected string literal", json::parse_error);
|
||||
}
|
||||
|
||||
SECTION("not ignored")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\" /* c */ : 1}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing object separator - invalid literal; last read: '\"a\" /'; expected ':'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1, \"b\" /* c */ : 2}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing object separator - invalid literal; last read: '\"b\" /'; expected ':'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , \"b\": 2}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 9: syntax error while parsing object - invalid literal; last read: '1 /'; expected '}'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , 2]")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing array - invalid literal; last read: '1 /'; expected ']'", json::parse_error);
|
||||
CHECK(!json::accept(std::string("[1 /* c */ , 2]")));
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Macro for all test cases for start_pos and end_pos
|
||||
#define SETUP_TESTCASES() \
|
||||
|
||||
@@ -191,8 +191,10 @@ TEST_CASE("value conversion")
|
||||
{
|
||||
enum class bool_enum : bool { off, on };
|
||||
|
||||
CHECK(json(bool_enum::off).get<bool_enum>() == bool_enum::off);
|
||||
CHECK(json(bool_enum::on).get<bool_enum>() == bool_enum::on);
|
||||
// the extra parentheses keep doctest from printing the enum via its
|
||||
// underlying type, which MSVC 2015 reports as C4800
|
||||
CHECK((json(bool_enum::off).get<bool_enum>() == bool_enum::off));
|
||||
CHECK((json(bool_enum::on).get<bool_enum>() == bool_enum::on));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -851,8 +853,11 @@ TEST_CASE("std::optional")
|
||||
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
||||
|
||||
// the conversion is noexcept exactly when converting the contained value is
|
||||
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
|
||||
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
|
||||
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
||||
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using nlohmann::json;
|
||||
#include <cstdint>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -109,6 +110,10 @@ JSON_TEST_DETECT(ptr_at, std::declval<J&>().at(std::declval<const typename J::js
|
||||
|
||||
TEST_CASE("JSON_DELETE_DEPRECATED_FUNCTIONS")
|
||||
{
|
||||
// MSVC 2015 does not treat selecting a deleted function in decltype as a
|
||||
// substitution failure, so the traits cannot tell deleted functions apart
|
||||
// there; calling them still fails to compile
|
||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910)
|
||||
SECTION("from_* with a pointer and a length")
|
||||
{
|
||||
// the overloads are deleted rather than removed, so the length cannot
|
||||
@@ -197,6 +202,8 @@ TEST_CASE("JSON_DELETE_DEPRECATED_FUNCTIONS")
|
||||
CHECK(ptr_at<json>::value);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
SECTION("the non-deprecated functions still work")
|
||||
{
|
||||
const json j = {{"a", {1, 2}}};
|
||||
@@ -210,5 +217,13 @@ TEST_CASE("JSON_DELETE_DEPRECATED_FUNCTIONS")
|
||||
CHECK(j.at(ptr) == 2);
|
||||
CHECK(j.value(ptr, 0) == 2);
|
||||
CHECK(j.contains(ptr));
|
||||
|
||||
std::ostringstream os;
|
||||
os << j;
|
||||
CHECK(os.str() == R"({"a":[1,2]})");
|
||||
std::istringstream is(os.str());
|
||||
json parsed;
|
||||
is >> parsed;
|
||||
CHECK(parsed == j);
|
||||
}
|
||||
}
|
||||
@@ -1551,6 +1551,10 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
||||
// count(0) used to compile and then crash instead of failing to compile
|
||||
using nlohmann::detail::is_detected;
|
||||
|
||||
// MSVC 2015 does not treat selecting a deleted function in decltype as
|
||||
// a substitution failure, so it detects the deleted overloads as
|
||||
// callable; calling them still fails to compile
|
||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910)
|
||||
CHECK_FALSE(is_detected<can_call_find_with_0, Json&>::value);
|
||||
CHECK_FALSE(is_detected<can_call_find_with_0, const Json&>::value);
|
||||
CHECK_FALSE(is_detected<can_call_count_with_0, Json&>::value);
|
||||
@@ -1562,6 +1566,7 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
||||
|
||||
// another integral literal type must be rejected as well, not just int
|
||||
CHECK_FALSE(is_detected<can_call_contains_with_0L, Json&>::value);
|
||||
#endif
|
||||
|
||||
// the valid overloads must remain callable
|
||||
CHECK(is_detected<can_call_find, Json&, const char*>::value);
|
||||
|
||||
@@ -41,10 +41,49 @@ TEST_CASE("MessagePack")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to MessagePack
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_msgpack(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
|
||||
@@ -40,7 +40,9 @@ using ordered_json = nlohmann::ordered_json;
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <list>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -107,6 +109,84 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// An allocator whose allocate() can be told to fail on demand, so tests can
|
||||
// check that ~basic_json() tolerates - in fact, after #5135, never even
|
||||
// triggers - an allocation failure. This replaces an earlier version of
|
||||
// this test that overrode the process-wide ::operator new/::operator
|
||||
// delete, which affected every allocation in the whole unit-regression2
|
||||
// binary rather than just the values under test.
|
||||
std::size_t failing_allocator_allocations = 0;
|
||||
std::size_t failing_allocator_deallocations = 0;
|
||||
bool fail_next_allocation = false;
|
||||
|
||||
template<class T>
|
||||
struct failing_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
failing_allocator() noexcept = default;
|
||||
template<class U>
|
||||
failing_allocator(const failing_allocator<U>& /*unused*/) noexcept {} // NOLINT(google-explicit-constructor)
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
if (fail_next_allocation)
|
||||
{
|
||||
fail_next_allocation = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
++failing_allocator_allocations;
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
++failing_allocator_deallocations;
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = failing_allocator<U>;
|
||||
};
|
||||
};
|
||||
|
||||
using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||
using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
|
||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||
|
||||
// builds `depth` levels of nesting around a scalar, iteratively (never
|
||||
// recursing: each wrap only moves the previous, already-built value, which
|
||||
// is O(1)), each level an array or an object depending on `nest_objects`
|
||||
template<class BasicJsonType>
|
||||
BasicJsonType make_deep_nest(std::size_t depth, bool nest_objects)
|
||||
{
|
||||
BasicJsonType v = 0;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (nest_objects)
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::object();
|
||||
wrapper["x"] = std::move(v);
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::array();
|
||||
wrapper.push_back(std::move(v));
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #1647
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -940,4 +1020,211 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("regression test #5135 - destructor never allocates, even under memory pressure")
|
||||
{
|
||||
// Before the fix, ~basic_json() flattened a nested array/object into a
|
||||
// heap-allocated std::vector to avoid recursing; that allocation could
|
||||
// itself throw bad_alloc, which escapes a noexcept destructor and
|
||||
// terminates the program. destroy() no longer allocates anything, so
|
||||
// none of the sections below ever observe fail_next_allocation being
|
||||
// consumed: CHECK(fail_next_allocation) confirms it was never touched.
|
||||
|
||||
SECTION("the original report: a small, mixed array/object nest")
|
||||
{
|
||||
failing_allocator_allocations = 0;
|
||||
failing_allocator_deallocations = 0;
|
||||
{
|
||||
failing_json j = failing_json::array(
|
||||
{
|
||||
failing_json::array({1, 2}),
|
||||
failing_json::object({{"key", failing_json::array({3})}})
|
||||
});
|
||||
fail_next_allocation = true;
|
||||
} // j is destroyed here, with every further allocation set to fail
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_deallocations > 0);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested array")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json j = make_deep_nest<failing_json>(100000, false);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested object")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json j = make_deep_nest<failing_json>(100000, true);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested ordered_json")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_ordered_json j = make_deep_nest<failing_ordered_json>(100000, true);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("wide and deep: 1000 arrays of 1000 elements, each a small nested object")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json wide = failing_json::array();
|
||||
for (std::size_t i = 0; i < 1000; ++i)
|
||||
{
|
||||
failing_json inner = failing_json::array();
|
||||
for (std::size_t k = 0; k < 1000; ++k)
|
||||
{
|
||||
inner.push_back(failing_json::object({{"a", 1}, {"b", failing_json::array({1, 2, 3})}}));
|
||||
}
|
||||
wide.push_back(std::move(inner));
|
||||
}
|
||||
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// a single-element chain of `depth` arrays, built iteratively (never
|
||||
// recursing: each wrap only moves the previous, already-built value)
|
||||
template<class BasicJsonType>
|
||||
BasicJsonType make_single_chain(std::size_t depth)
|
||||
{
|
||||
BasicJsonType v = 1;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::array();
|
||||
wrapper.push_back(std::move(v));
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// copies value first, to make sure nothing was corrupted by building it,
|
||||
// then lets both the copy and the original destruct via normal scope exit
|
||||
template<class BasicJsonType>
|
||||
void check_destroy_edge_case(const BasicJsonType& value)
|
||||
{
|
||||
const BasicJsonType copy = value;
|
||||
CHECK(copy == value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType, json, ordered_json)
|
||||
{
|
||||
using binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
SECTION("mix of empty objects, empty arrays, non-empty containers, and scalars")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
root.push_back(BasicJsonType::object());
|
||||
root.push_back(BasicJsonType::array());
|
||||
root.push_back(BasicJsonType::object({{"k", 1}}));
|
||||
root.push_back(BasicJsonType::array({1, 2, 3}));
|
||||
root.push_back(nullptr);
|
||||
root.push_back(true);
|
||||
root.push_back(42);
|
||||
root.push_back(3.14);
|
||||
root.push_back("a string");
|
||||
root.push_back(BasicJsonType(binary_t({1, 2, 3})));
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container child in first position only")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1, 2}), 3, 4, 5});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container child in last position only")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({1, 2, 3, BasicJsonType::array({4, 5})});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container children in first and last position")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1}), 2, 3, BasicJsonType::array({4})});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("single-element chain, 1000 levels deep")
|
||||
{
|
||||
BasicJsonType root = make_single_chain<BasicJsonType>(1000);
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("top-level empty array")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("top-level empty object")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::object();
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("object whose last child is a non-empty array whose last child is an empty object")
|
||||
{
|
||||
BasicJsonType inner_array = BasicJsonType::array({1, 2, BasicJsonType::object()});
|
||||
BasicJsonType root = BasicJsonType::object({{"a", 1}, {"b", inner_array}});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("destruction via erase() on a deeply nested child")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
root.push_back(make_single_chain<BasicJsonType>(500));
|
||||
root.push_back(BasicJsonType::object({{"k", BasicJsonType::array({1, 2, 3})}}));
|
||||
// erase() must destroy the removed subtree without recursing or
|
||||
// allocating beyond what erase() itself needs
|
||||
root.erase(0);
|
||||
CAPTURE(root.size())
|
||||
CHECK(root.size() == 1);
|
||||
}
|
||||
|
||||
SECTION("destruction via assignment on a deep tree")
|
||||
{
|
||||
BasicJsonType root = make_single_chain<BasicJsonType>(2000);
|
||||
// assigning a new value destroys the old one in place
|
||||
root = nullptr;
|
||||
CHECK(root.is_null());
|
||||
}
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -35,10 +35,59 @@ TEST_CASE("UBJSON")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to UBJSON
|
||||
json const j = json::value_t::discarded;
|
||||
const auto result = json::to_ubjson(j);
|
||||
CHECK(result.empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("optimized array of all-discarded elements")
|
||||
{
|
||||
json const j = {discarded, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
@@ -2099,9 +2148,14 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
// a discarded value cannot be serialized to UBJSON, even as part
|
||||
// of an optimized array of a single (here: valueless) type
|
||||
json const j = {json::value_t::discarded, json::value_t::discarded};
|
||||
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
|
||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user