mirror of
https://github.com/nlohmann/json.git
synced 2026-10-09 16:07:14 +00:00
Reduce test compile times (spike)
Two changes roughly halve the CPU time needed to build the unit tests (clang 21: ~300 s -> ~150 s, GCC 16: ~750-820 s -> ~395-420 s, Debug): - Move the C++14/17/20-dependent tests into separate unit-<name>-cpp<N>.cpp files. A test file is built for every standard whose JSON_HAS_CPP_<N> macro it mentions, so far whole large files were rebuilt for C++14/17/20 because of a few #ifdef sections. The main files are now built for C++11 only; the ci_test_*_cxx<N> jobs still build every file for every standard. - Add the CMake option JSON_TestUnityBuild (ON by default, OFF with MinGW): compatible test files are compiled in batches as one translation unit so they share the template instantiations of the library. Each file keeps its own CTest test, which runs the batch executable filtered to that file's test cases. The binary-format tests form an explicit group; the rest is batched by JSON_TestUnityBatchSize. Fix the name clashes that merging files exposed, document the rules for test files in tests/README.md, and point CONTRIBUTING.md to it. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
44 files changed
+3965
-2225
No files matched your search
+36
-3
@@ -2,12 +2,14 @@ cmake_minimum_required(VERSION 3.13...4.0)
|
||||
|
||||
option(JSON_Valgrind "Execute test suite with Valgrind." OFF)
|
||||
option(JSON_FastTests "Skip expensive/slow tests." OFF)
|
||||
option(JSON_TestUnityBuild "Build the unit tests in batches of several files per executable to speed up compilation." ON)
|
||||
option(JSON_TestSimdutf "Build the unit tests against the simdutf UTF-8 validation backend." OFF)
|
||||
|
||||
set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_TestSimdutf.")
|
||||
|
||||
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_TestUnityBatchSize 8 CACHE STRING "The number of unit test files compiled into one executable with JSON_TestUnityBuild.")
|
||||
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")
|
||||
@@ -20,6 +22,11 @@ endif()
|
||||
|
||||
include(test)
|
||||
|
||||
# MinGW's GNU ld hits section and relocation limits with the large merged translation units
|
||||
if(MINGW)
|
||||
set(JSON_TestUnityBuild OFF)
|
||||
endif()
|
||||
|
||||
#############################################################################
|
||||
# override standard support
|
||||
#############################################################################
|
||||
@@ -322,9 +329,28 @@ if(NOT "${JSON_TestShard}" STREQUAL "")
|
||||
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()
|
||||
# Explicit groups of test files (file names without "unit-" and ".cpp") for
|
||||
# JSON_TestUnityBuild: all files of a group are compiled into one translation
|
||||
# unit per C++ standard, so that related tests share their template
|
||||
# instantiations (all binary formats instantiate the same readers and
|
||||
# writers). Only add a group where this was measured to pay off: grouping
|
||||
# files that share little just creates one slow translation unit. Files in no
|
||||
# group are batched alphabetically by JSON_TestUnityBatchSize.
|
||||
set(json_test_unity_groups binary)
|
||||
set(json_test_unity_group_binary binary_formats binary_utf8_error_handler binary_writer_sinks bjdata bon8 bon8-cpp17 bson byte_container_with_subtype cbor msgpack msgpack-cpp17 ubjson)
|
||||
|
||||
if(JSON_TestUnityBuild)
|
||||
message(STATUS "Building the unit tests in batches of ${JSON_TestUnityBatchSize} files (JSON_TestUnityBuild)")
|
||||
json_test_add_unity_tests(FILES ${files} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
BATCH_SIZE ${JSON_TestUnityBatchSize}
|
||||
GROUPS ${json_test_unity_groups}
|
||||
VARIANT_FILES src/unit-comparison.cpp src/unit-comparison-cpp20.cpp src/unit-class_parser.cpp src/unit-diagnostic-positions.cpp
|
||||
)
|
||||
else()
|
||||
foreach(file ${files})
|
||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
|
||||
# so its test targets must be built after generate_hedley_undef_checks.
|
||||
@@ -350,6 +376,13 @@ json_test_add_test_for(src/unit-comparison.cpp
|
||||
NAME test-comparison_legacy
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
json_test_set_test_options(test-comparison_legacy-cpp20
|
||||
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-comparison-cpp20.cpp
|
||||
NAME test-comparison_legacy-cpp20
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
|
||||
json_test_set_test_options(test-class_parser_diagnostic_positions
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# Unit tests
|
||||
|
||||
The unit tests are in [`src/unit-*.cpp`](src) and use [doctest](https://github.com/doctest/doctest). Each file becomes
|
||||
one CTest test per C++ standard it is built for, named after the file: `src/unit-foo.cpp` becomes `test-foo_cpp11`.
|
||||
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
cmake -S . -B build -DJSON_BuildTests=ON
|
||||
cmake --build build -j 10
|
||||
ctest --test-dir build -j 10
|
||||
```
|
||||
|
||||
The [CMake options](../docs/mkdocs/docs/integration/cmake.md) starting with `JSON_Test` (and `JSON_FastTests`,
|
||||
`JSON_Valgrind`) control the test build. The most relevant ones:
|
||||
|
||||
- `JSON_TestStandards`: build every test file for the given standards, e.g., `-DJSON_TestStandards=17`. By default,
|
||||
a file is built for C++11 and for each standard whose `JSON_HAS_CPP_<N>` macro it mentions (see below).
|
||||
- `JSON_TestUnityBuild`: compile several test files together (see below). Set it to `OFF` to get one executable per
|
||||
test file, e.g., to debug a single test in a debugger.
|
||||
- `JSON_TestShard`: build only a part of the test files, e.g., `-DJSON_TestShard=0/2`.
|
||||
|
||||
To run the tests of one file only, run its CTest test, e.g., `ctest --test-dir build -R test-foo_cpp11`.
|
||||
|
||||
## How test files are built
|
||||
|
||||
Two mechanisms keep the compile time down. Both are automatic, but they set a few rules for test files.
|
||||
|
||||
### C++ standards
|
||||
|
||||
A test file is built for a C++ standard beyond C++11 if its text contains `JSON_HAS_CPP_<N>`, for instance in
|
||||
`#ifdef JSON_HAS_CPP_17`, or in a comment `// JSON_HAS_CPP_17` next to code that needs a macro only defined for that
|
||||
standard, such as `JSON_HAS_FILESYSTEM`. Then the *whole file* is compiled again for that standard.
|
||||
|
||||
So that this does not happen for the large files, tests that need C++14, C++17, or C++20 go into a separate file
|
||||
`src/unit-<name>-cpp<N>.cpp` (e.g., [`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp)), whose body
|
||||
is wrapped in `#ifdef JSON_HAS_CPP_<N>`. The main file `src/unit-<name>.cpp` must not mention `JSON_HAS_CPP_<N>` at
|
||||
all, not even in a comment, so it is built for C++11 only. The CI jobs `ci_test_*_cxx<N>` still build every test file
|
||||
for every standard.
|
||||
|
||||
### Unity build
|
||||
|
||||
With `JSON_TestUnityBuild` (`ON` by default, `OFF` with MinGW), CMake compiles several test files as one translation
|
||||
unit, so they share the template instantiations of the library. CMake generates `build/tests/unity/test-unity-*.cpp`,
|
||||
which `#include` the test files of a batch. Every test file still gets its own CTest test, which runs the batch
|
||||
executable with `--source-file=*unit-foo.cpp`, so only the test cases of that file run.
|
||||
|
||||
CMake decides from the macros a file defines *before* including `<nlohmann/json.hpp>`:
|
||||
|
||||
| Macros defined before the include | Built as |
|
||||
|-------------------------------------------------------------------------------------------|-----------------------------------|
|
||||
| none (or only `SKIP_TESTS_FOR_*` and similar macros derived from global definitions) | batch with the other plain files |
|
||||
| only `JSON_TESTS_PRIVATE` | batch with the other such files |
|
||||
| any library configuration macro (`JSON_DIAGNOSTICS`, `JSON_NO_IO`, `JSON_ASSERT`, ...) | its own executable |
|
||||
|
||||
Files with test-specific build options (`json_test_set_test_options(test-foo ...)` in
|
||||
[`CMakeLists.txt`](CMakeLists.txt)) also get their own executable. The batchable files are split alphabetically into
|
||||
batches of `JSON_TestUnityBatchSize` files, except for explicit groups of related files: `json_test_unity_group_binary`
|
||||
compiles all binary-format tests together, because they share the binary readers and writers. Only add a group if it
|
||||
measurably pays off; files that share little just make one slow translation unit.
|
||||
|
||||
## Rules for test files
|
||||
|
||||
Because the files of a batch share one translation unit, a test file must not affect the files that follow it:
|
||||
|
||||
1. **Give file-scope helpers file-specific names.** An anonymous namespace does not help, as the batch is one
|
||||
translation unit. Use a name such as `my_allocator_2982`, or wrap the helpers in a namespace named after the file
|
||||
(e.g., `namespace unit_comparison_detail`). Clashes show up as compile errors.
|
||||
2. **Do not `#undef` library macros**, such as `JSON_HAS_CPP_17`, at the end of a file: this silently disables the tests
|
||||
that depend on them in the later files of the batch. `#undef` macros you define yourself after including the library.
|
||||
3. **Put version-dependent tests into `unit-<name>-cpp<N>.cpp`** (see [C++ standards](#c-standards)).
|
||||
4. **Define test cases in the `.cpp` file itself.** The batch executable selects the tests of a file by its name, so a
|
||||
`TEST_CASE` in a shared header is not run. The only exception is the test case in
|
||||
[`src/make_test_data_available.hpp`](src/make_test_data_available.hpp), which CMake handles explicitly.
|
||||
|
||||
To check a new file for clashes with all others at once, build with `-DJSON_TestUnityBatchSize=1000`, which puts all
|
||||
batchable files of a kind into one translation unit.
|
||||
|
||||
## Where to add tests
|
||||
|
||||
Tests are structured along the features of the library. Usually, an existing file is the right place:
|
||||
|
||||
- For a bug fix, add a section referencing the issue to [`src/unit-regression3.cpp`](src/unit-regression3.cpp), or to
|
||||
[`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp) /
|
||||
[`src/unit-regression3-cpp20.cpp`](src/unit-regression3-cpp20.cpp) if the test needs C++17 / C++20.
|
||||
[`src/unit-regression2.cpp`](src/unit-regression2.cpp) holds older tests and should not grow further.
|
||||
- When testing exceptions, use `CHECK_THROWS_WITH_AS`, which also checks the `what()` message.
|
||||
|
||||
A new file `src/unit-<name>.cpp` is picked up automatically when CMake runs again; no change to `CMakeLists.txt` is
|
||||
needed.
|
||||
|
||||
See also [`fuzzing.md`](fuzzing.md) for fuzz testing.
|
||||
@@ -0,0 +1,98 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-allocator.cpp (std::string_view key
|
||||
// lookup with a failing allocator). It is kept in a separate translation unit so the (much
|
||||
// larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every C++
|
||||
// standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool next_construct_fails_cpp17 = false;
|
||||
bool next_destroy_fails_cpp17 = false;
|
||||
bool next_deallocate_fails_cpp17 = false;
|
||||
|
||||
template<class T>
|
||||
struct my_allocator_cpp17 : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class... Args>
|
||||
void construct(T* p, Args&& ... args)
|
||||
{
|
||||
if (next_construct_fails_cpp17)
|
||||
{
|
||||
next_construct_fails_cpp17 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
if (next_deallocate_fails_cpp17)
|
||||
{
|
||||
next_deallocate_fails_cpp17 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
void destroy(T* p)
|
||||
{
|
||||
if (next_destroy_fails_cpp17)
|
||||
{
|
||||
next_destroy_fails_cpp17 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
static_cast<void>(p); // fix MSVC's C4100 warning
|
||||
p->~T();
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = my_allocator_cpp17<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||
// next_construct_fails_cpp17 set for the next allocation outside a check
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("a failed allocation leaves the value unchanged (C++17)")
|
||||
{
|
||||
// create JSON type using the throwing allocator
|
||||
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp17>;
|
||||
|
||||
SECTION("turning a null value into an array or object")
|
||||
{
|
||||
my_json j;
|
||||
|
||||
next_construct_fails_cpp17 = true;
|
||||
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
next_construct_fails_cpp17 = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-allocator.cpp (creating a value from a
|
||||
// std::ranges view with a failing allocator). It is kept in a separate translation unit so
|
||||
// the (much larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every
|
||||
// C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool next_construct_fails_cpp20 = false;
|
||||
bool next_destroy_fails_cpp20 = false;
|
||||
bool next_deallocate_fails_cpp20 = false;
|
||||
|
||||
template<class T>
|
||||
struct my_allocator_cpp20 : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class... Args>
|
||||
void construct(T* p, Args&& ... args)
|
||||
{
|
||||
if (next_construct_fails_cpp20)
|
||||
{
|
||||
next_construct_fails_cpp20 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
if (next_deallocate_fails_cpp20)
|
||||
{
|
||||
next_deallocate_fails_cpp20 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
void destroy(T* p)
|
||||
{
|
||||
if (next_destroy_fails_cpp20)
|
||||
{
|
||||
next_destroy_fails_cpp20 = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
static_cast<void>(p); // fix MSVC's C4100 warning
|
||||
p->~T();
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = my_allocator_cpp20<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||
// next_construct_fails_cpp20 set for the next allocation outside a check
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("a failed allocation leaves the value unchanged (C++20)")
|
||||
{
|
||||
// create JSON type using the throwing allocator
|
||||
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp20>;
|
||||
|
||||
// With iterator debugging, VS 2015's containers construct a proxy with the
|
||||
// allocator in constructors that cannot report its failure, so a failing
|
||||
// allocator crashes this section there (SIGSEGV with VS 2015 Debug x86).
|
||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||
SECTION("converting into an existing value")
|
||||
{
|
||||
// to_json replaces the value it is given; the old one must survive a
|
||||
// failed creation of the new one
|
||||
my_json j = "old";
|
||||
|
||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||
const std::vector<int> numbers = {1, 2};
|
||||
next_construct_fails_cpp20 = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||
{
|
||||
return true;
|
||||
})), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
#endif
|
||||
|
||||
next_construct_fails_cpp20 = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -13,9 +13,6 @@
|
||||
using nlohmann::json;
|
||||
|
||||
#include <valarray>
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -684,12 +681,6 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
#endif
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
@@ -787,16 +778,6 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||
const std::vector<int> numbers = {1, 2};
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||
{
|
||||
return true;
|
||||
})), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
#endif
|
||||
|
||||
next_construct_fails = false;
|
||||
nlohmann::to_json(j, std::vector<int> {1, 2});
|
||||
CHECK(j == my_json({1, 2}));
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-bon8.cpp (BON8 with std::byte
|
||||
// containers). It is kept in a separate translation unit so the (much larger) unit-
|
||||
// bon8.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("BON8 with std::byte")
|
||||
{
|
||||
SECTION("vector roundtrip")
|
||||
{
|
||||
const json original =
|
||||
{
|
||||
{"name", "test"},
|
||||
{"value", 42},
|
||||
{"array", {1, 2, 3}}
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||
std::vector<std::byte> bon8_data(temp.size());
|
||||
for (size_t i = 0; i < temp.size(); ++i)
|
||||
{
|
||||
bon8_data[i] = std::byte(temp[i]);
|
||||
}
|
||||
|
||||
json from_bytes;
|
||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
||||
CHECK(from_bytes == original);
|
||||
}
|
||||
|
||||
SECTION("empty vector")
|
||||
{
|
||||
const std::vector<std::byte> empty_data;
|
||||
CHECK_THROWS_WITH_AS([&]()
|
||||
{
|
||||
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
||||
return true;
|
||||
}
|
||||
(),
|
||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||
json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1052,41 +1052,3 @@ TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
TEST_CASE("BON8 with std::byte")
|
||||
{
|
||||
SECTION("vector roundtrip")
|
||||
{
|
||||
const json original =
|
||||
{
|
||||
{"name", "test"},
|
||||
{"value", 42},
|
||||
{"array", {1, 2, 3}}
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||
std::vector<std::byte> bon8_data(temp.size());
|
||||
for (size_t i = 0; i < temp.size(); ++i)
|
||||
{
|
||||
bon8_data[i] = std::byte(temp[i]);
|
||||
}
|
||||
|
||||
json from_bytes;
|
||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
||||
CHECK(from_bytes == original);
|
||||
}
|
||||
|
||||
SECTION("empty vector")
|
||||
{
|
||||
const std::vector<std::byte> empty_data;
|
||||
CHECK_THROWS_WITH_AS([&]()
|
||||
{
|
||||
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
||||
return true;
|
||||
}
|
||||
(),
|
||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||
json::parse_error&);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,496 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-comparison.cpp (tests of operator<=> and
|
||||
// other three-way comparison specific behavior). It is kept in a separate translation unit
|
||||
// so the (much larger) unit-comparison.cpp is built for C++11 only and not rebuilt for
|
||||
// every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
#include <cmath>
|
||||
#include <compare>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// this can be replaced with the doctest stl extension header in version 2.5
|
||||
namespace doctest
|
||||
{
|
||||
template<> struct StringMaker<std::partial_ordering>
|
||||
{
|
||||
static String convert(const std::partial_ordering& order)
|
||||
{
|
||||
if (order == std::partial_ordering::less)
|
||||
{
|
||||
return "std::partial_ordering::less";
|
||||
}
|
||||
if (order == std::partial_ordering::equivalent)
|
||||
{
|
||||
return "std::partial_ordering::equivalent";
|
||||
}
|
||||
if (order == std::partial_ordering::greater)
|
||||
{
|
||||
return "std::partial_ordering::greater";
|
||||
}
|
||||
if (order == std::partial_ordering::unordered)
|
||||
{
|
||||
return "std::partial_ordering::unordered";
|
||||
}
|
||||
return "{?}";
|
||||
}
|
||||
};
|
||||
} // namespace doctest
|
||||
|
||||
TEST_CASE("lexicographical comparison operators (C++20)")
|
||||
{
|
||||
constexpr auto f_ = false;
|
||||
constexpr auto _t = true;
|
||||
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||
constexpr auto lt = std::partial_ordering::less;
|
||||
constexpr auto gt = std::partial_ordering::greater;
|
||||
constexpr auto eq = std::partial_ordering::equivalent;
|
||||
constexpr auto un = std::partial_ordering::unordered;
|
||||
|
||||
INFO("using 3-way comparison");
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
INFO("using legacy comparison");
|
||||
#endif
|
||||
|
||||
SECTION("types")
|
||||
{
|
||||
std::vector<json::value_t> j_types =
|
||||
{
|
||||
json::value_t::null,
|
||||
json::value_t::boolean,
|
||||
json::value_t::number_integer,
|
||||
json::value_t::number_unsigned,
|
||||
json::value_t::number_float,
|
||||
json::value_t::object,
|
||||
json::value_t::array,
|
||||
json::value_t::string,
|
||||
json::value_t::binary,
|
||||
json::value_t::discarded
|
||||
};
|
||||
|
||||
std::vector<std::vector<bool>> expected_lt =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9
|
||||
{f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1
|
||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2
|
||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3
|
||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4
|
||||
{f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5
|
||||
{f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
||||
};
|
||||
|
||||
SECTION("comparison: less")
|
||||
{
|
||||
for (size_t i = 0; i < j_types.size(); ++i)
|
||||
{
|
||||
for (size_t j = 0; j < j_types.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
// check precomputed values
|
||||
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
||||
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
||||
REQUIRE(std::isnan(nan));
|
||||
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9
|
||||
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
||||
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
||||
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
||||
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
||||
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
||||
{un, un, un, un, un, un, un, un, un, un}, // 9
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected boolean
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
// check 3-way comparison against expected partial_ordering
|
||||
REQUIRE(expected.size() == j_types.size());
|
||||
for (size_t i = 0; i < j_types.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_types.size());
|
||||
for (size_t j = 0; j < j_types.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values")
|
||||
{
|
||||
json j_values =
|
||||
{
|
||||
nullptr, nullptr, // 0 1
|
||||
-17, 42, // 2 3
|
||||
8u, 13u, // 4 5
|
||||
3.14159, 23.42, // 6 7
|
||||
nan, nan, // 8 9
|
||||
"foo", "bar", // 10 11
|
||||
true, false, // 12 13
|
||||
{1, 2, 3}, {"one", "two", "three"}, // 14 15
|
||||
{{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17
|
||||
json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19
|
||||
json(json::value_t::discarded), json(json::value_t::discarded) // 20 21
|
||||
};
|
||||
|
||||
std::vector<std::vector<bool>> expected_eq =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
|
||||
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
|
||||
{f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
|
||||
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
|
||||
{f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
|
||||
{f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
|
||||
{f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
|
||||
{f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
||||
};
|
||||
|
||||
std::vector<std::vector<bool>> expected_lt =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1
|
||||
{f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3
|
||||
{f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4
|
||||
{f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5
|
||||
{f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6
|
||||
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12
|
||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
||||
};
|
||||
|
||||
SECTION("signed/unsigned mixed comparison above INT64_MAX")
|
||||
{
|
||||
const json above_int64_max = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL;
|
||||
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||
const json negative_one = -1;
|
||||
const json one = 1;
|
||||
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||
|
||||
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
}
|
||||
|
||||
SECTION("integer/float mixed comparison is exact")
|
||||
{
|
||||
// Widening the integer to a double loses precision past the
|
||||
// mantissa, so 2^63-2 and 2^63-1 both used to compare equal to the
|
||||
// double 2^63 while differing from each other. That makes equality
|
||||
// intransitive and the ordering not a strict weak ordering.
|
||||
const json below_two_63 = static_cast<std::int64_t>(9223372036854775806LL);
|
||||
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
||||
const json two_63 = 9223372036854775808.0;
|
||||
|
||||
// the same past the unsigned range
|
||||
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
||||
const json two_64 = 18446744073709551616.0;
|
||||
|
||||
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
||||
}
|
||||
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
||||
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
||||
REQUIRE(std::isnan(nan));
|
||||
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
||||
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
||||
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
||||
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
||||
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
||||
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
||||
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected booleans
|
||||
REQUIRE(expected.size() == expected_eq.size());
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_eq[i].size());
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
if (std::is_gt(expected[i][j]))
|
||||
{
|
||||
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check that two values compare according to their expected ordering
|
||||
REQUIRE(expected.size() == j_values.size());
|
||||
for (size_t i = 0; i < j_values.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_values.size());
|
||||
for (size_t j = 0; j < j_values.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
||||
{
|
||||
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
||||
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
||||
// Verify that heterogeneous comparisons now work.
|
||||
|
||||
SECTION("string vs json")
|
||||
{
|
||||
std::string s = "string";
|
||||
json j = "string";
|
||||
CHECK(s == j);
|
||||
CHECK(j == s);
|
||||
CHECK_FALSE(s != j);
|
||||
CHECK_FALSE(j != s);
|
||||
}
|
||||
|
||||
SECTION("other heterogeneous types")
|
||||
{
|
||||
int i = 42;
|
||||
json j = 42;
|
||||
CHECK(i == j);
|
||||
CHECK(j == i);
|
||||
CHECK_FALSE(i != j);
|
||||
CHECK_FALSE(j != i);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
||||
{
|
||||
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
||||
// candidate rewritten from operator<=>, which does not emulate the legacy
|
||||
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
||||
// other three operand orders.
|
||||
const json discarded(json::value_t::discarded);
|
||||
const json one = 1;
|
||||
|
||||
CHECK(discarded <= 1);
|
||||
CHECK(discarded >= 1);
|
||||
CHECK(one <= discarded);
|
||||
CHECK(one >= discarded);
|
||||
CHECK(1 <= discarded);
|
||||
CHECK(1 >= discarded);
|
||||
CHECK(1.5 <= discarded);
|
||||
CHECK(1.5 >= discarded);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE("containers are compared element by element (C++20)")
|
||||
{
|
||||
// Containers nested deeper than a bound are compared without the call
|
||||
// stack, by code of their own; every relation is checked both at the top
|
||||
// level and below that bound.
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
// objects with different keys
|
||||
{
|
||||
const json a = deep({{"a", 1}}, depth);
|
||||
const json b = deep({{"b", 1}}, depth);
|
||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
}
|
||||
|
||||
// a container that is a prefix of the other one
|
||||
{
|
||||
// the one that runs out of elements first is the smaller one
|
||||
const json shorter = deep({1}, depth);
|
||||
const json longer = deep({1, 2}, depth);
|
||||
|
||||
const json smaller_object = deep({{"a", 1}}, depth);
|
||||
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
|
||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||
}
|
||||
|
||||
// elements that cannot be ordered
|
||||
{
|
||||
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
const json lhs = deep({nan, 1}, depth);
|
||||
const json rhs = deep({nan, 2}, depth);
|
||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||
// does, and operator< is derived from it
|
||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK_FALSE(lhs < rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||
{
|
||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||
// pair of binary values with the same bytes but a different subtype is
|
||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||
// that a NaN has. Within the nesting bound, an array compares itself
|
||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||
// undecided and lets the next element decide, same as
|
||||
// std::lexicographical_compare_three_way does. Past the bound,
|
||||
// compare_iteratively<true>() takes over and must classify the pair the
|
||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||
// from it - depends on how deeply the values are nested.
|
||||
const json a = json::array({json::binary({1}, 1), 1});
|
||||
const json b = json::array({json::binary({1}, 2), 2});
|
||||
|
||||
// the root inconsistency: unequal, yet <=>-equivalent
|
||||
CHECK_FALSE(a[0] == b[0]);
|
||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||
// and must still agree with the levels that do
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
const json x = deep(a, depth);
|
||||
const json y = deep(b, depth);
|
||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK(x < y);
|
||||
CHECK(y > x);
|
||||
CHECK_FALSE(y < x);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // JSON_HAS_THREE_WAY_COMPARISON
|
||||
#endif
|
||||
+11
-305
@@ -6,12 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-comparison-cpp<NN>.cpp. This file stays C++11-only.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -28,37 +27,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// this can be replaced with the doctest stl extension header in version 2.5
|
||||
namespace doctest
|
||||
{
|
||||
template<> struct StringMaker<std::partial_ordering>
|
||||
{
|
||||
static String convert(const std::partial_ordering& order)
|
||||
{
|
||||
if (order == std::partial_ordering::less)
|
||||
{
|
||||
return "std::partial_ordering::less";
|
||||
}
|
||||
if (order == std::partial_ordering::equivalent)
|
||||
{
|
||||
return "std::partial_ordering::equivalent";
|
||||
}
|
||||
if (order == std::partial_ordering::greater)
|
||||
{
|
||||
return "std::partial_ordering::greater";
|
||||
}
|
||||
if (order == std::partial_ordering::unordered)
|
||||
{
|
||||
return "std::partial_ordering::unordered";
|
||||
}
|
||||
return "{?}";
|
||||
}
|
||||
};
|
||||
} // namespace doctest
|
||||
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// helper function to check std::less<json::value_t>
|
||||
@@ -75,16 +43,6 @@ TEST_CASE("lexicographical comparison operators")
|
||||
constexpr auto f_ = false;
|
||||
constexpr auto _t = true;
|
||||
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
constexpr auto lt = std::partial_ordering::less;
|
||||
constexpr auto gt = std::partial_ordering::greater;
|
||||
constexpr auto eq = std::partial_ordering::equivalent;
|
||||
constexpr auto un = std::partial_ordering::unordered;
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
INFO("using 3-way comparison");
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
INFO("using legacy comparison");
|
||||
@@ -135,62 +93,13 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
// check precomputed values
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
||||
#else
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||
#endif
|
||||
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9
|
||||
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
||||
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
||||
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
||||
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
||||
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
||||
{un, un, un, un, un, un, un, un, un, un}, // 9
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected boolean
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
// check 3-way comparison against expected partial_ordering
|
||||
REQUIRE(expected.size() == j_types.size());
|
||||
for (size_t i = 0; i < j_types.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_types.size());
|
||||
for (size_t j = 0; j < j_types.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("values")
|
||||
@@ -319,18 +228,6 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CHECK_FALSE(above_int64_max <= max_int64);
|
||||
CHECK(above_int64_max > max_int64);
|
||||
CHECK(above_int64_max >= max_int64);
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("integer/float mixed comparison is exact")
|
||||
@@ -381,16 +278,6 @@ TEST_CASE("lexicographical comparison operators")
|
||||
CHECK_FALSE(json(1) < json(nan));
|
||||
CHECK_FALSE(json(nan) < json(1));
|
||||
CHECK_FALSE(json(1u) == json(nan));
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("compares unordered")
|
||||
@@ -617,72 +504,6 @@ TEST_CASE("lexicographical comparison operators")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
SECTION("comparison: 3-way")
|
||||
{
|
||||
std::vector<std::vector<std::partial_ordering>> expected =
|
||||
{
|
||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
||||
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
||||
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
||||
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
||||
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
||||
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
||||
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
||||
};
|
||||
|
||||
// check expected partial_ordering against expected booleans
|
||||
REQUIRE(expected.size() == expected_eq.size());
|
||||
REQUIRE(expected.size() == expected_lt.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == expected_eq[i].size());
|
||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||
if (std::is_gt(expected[i][j]))
|
||||
{
|
||||
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check that two values compare according to their expected ordering
|
||||
REQUIRE(expected.size() == j_values.size());
|
||||
for (size_t i = 0; i < j_values.size(); ++i)
|
||||
{
|
||||
REQUIRE(expected[i].size() == j_values.size());
|
||||
for (size_t j = 0; j < j_values.size(); ++j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CAPTURE(j)
|
||||
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
@@ -721,60 +542,7 @@ TEST_CASE("lexicographical comparison operators")
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
|
||||
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
||||
{
|
||||
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
||||
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
||||
// Verify that heterogeneous comparisons now work.
|
||||
|
||||
SECTION("string vs json")
|
||||
{
|
||||
std::string s = "string";
|
||||
json j = "string";
|
||||
CHECK(s == j);
|
||||
CHECK(j == s);
|
||||
CHECK_FALSE(s != j);
|
||||
CHECK_FALSE(j != s);
|
||||
}
|
||||
|
||||
SECTION("other heterogeneous types")
|
||||
{
|
||||
int i = 42;
|
||||
json j = 42;
|
||||
CHECK(i == j);
|
||||
CHECK(j == i);
|
||||
CHECK_FALSE(i != j);
|
||||
CHECK_FALSE(j != i);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
||||
{
|
||||
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
||||
// candidate rewritten from operator<=>, which does not emulate the legacy
|
||||
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
||||
// other three operand orders.
|
||||
const json discarded(json::value_t::discarded);
|
||||
const json one = 1;
|
||||
|
||||
CHECK(discarded <= 1);
|
||||
CHECK(discarded >= 1);
|
||||
CHECK(one <= discarded);
|
||||
CHECK(one >= discarded);
|
||||
CHECK(1 <= discarded);
|
||||
CHECK(1 >= discarded);
|
||||
CHECK(1.5 <= discarded);
|
||||
CHECK(1.5 >= discarded);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
namespace
|
||||
namespace unit_comparison_detail
|
||||
{
|
||||
// orders keys ascending or descending, as chosen when a map is created
|
||||
template<class Key>
|
||||
@@ -906,7 +674,9 @@ struct case_insensitive_less
|
||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
|
||||
using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
|
||||
} // namespace
|
||||
} // namespace unit_comparison_detail
|
||||
|
||||
using namespace unit_comparison_detail; // NOLINT(google-build-using-namespace)
|
||||
|
||||
TEST_CASE("equality of objects whose entries have no fixed order")
|
||||
{
|
||||
@@ -1049,12 +819,6 @@ TEST_CASE("containers are compared element by element")
|
||||
CHECK(a < b);
|
||||
CHECK(b > a);
|
||||
CHECK_FALSE(b < a);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// a container that is a prefix of the other one
|
||||
@@ -1072,11 +836,6 @@ TEST_CASE("containers are compared element by element")
|
||||
CHECK(smaller_object < larger_object);
|
||||
CHECK(larger_object > smaller_object);
|
||||
CHECK_FALSE(smaller_object == larger_object);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
// elements that cannot be ordered
|
||||
@@ -1087,13 +846,7 @@ TEST_CASE("containers are compared element by element")
|
||||
|
||||
CHECK_FALSE(lhs == lhs);
|
||||
CHECK_FALSE(rhs < lhs);
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||
// does, and operator< is derived from it
|
||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK_FALSE(lhs < rhs);
|
||||
#else
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON
|
||||
// operator< skips a pair of elements that cannot be ordered, as
|
||||
// std::lexicographical_compare does, and the next pair decides
|
||||
CHECK(lhs < rhs);
|
||||
@@ -1102,50 +855,3 @@ TEST_CASE("containers are compared element by element")
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||
{
|
||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||
// pair of binary values with the same bytes but a different subtype is
|
||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||
// that a NaN has. Within the nesting bound, an array compares itself
|
||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||
// undecided and lets the next element decide, same as
|
||||
// std::lexicographical_compare_three_way does. Past the bound,
|
||||
// compare_iteratively<true>() takes over and must classify the pair the
|
||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||
// from it - depends on how deeply the values are nested.
|
||||
const json a = json::array({json::binary({1}, 1), 1});
|
||||
const json b = json::array({json::binary({1}, 2), 2});
|
||||
|
||||
// the root inconsistency: unequal, yet <=>-equivalent
|
||||
CHECK_FALSE(a[0] == b[0]);
|
||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
|
||||
const auto deep = [](const json & j, const std::size_t depth)
|
||||
{
|
||||
json result = j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
result = json::array({std::move(result)});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||
// and must still agree with the levels that do
|
||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
const json x = deep(a, depth);
|
||||
const json y = deep(b, depth);
|
||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK(x < y);
|
||||
CHECK(y > x);
|
||||
CHECK_FALSE(y < x);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,95 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-conversions1.cpp (conversions to and from
|
||||
// std::string_view). It is kept in a separate translation unit so the (much larger) unit-
|
||||
// conversions1.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
|
||||
TEST_CASE("value conversion (C++17)")
|
||||
{
|
||||
SECTION("get a string (explicit)")
|
||||
{
|
||||
const json::string_t s_reference{"Hello world"};
|
||||
json j(s_reference);
|
||||
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
|
||||
SECTION("exception in case of a non-string type using string_view")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("get a string (explicit, get_to)")
|
||||
{
|
||||
const json::string_t s_reference{"Hello world"};
|
||||
json j(s_reference);
|
||||
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string const s = "previous value";
|
||||
std::string_view sv = s;
|
||||
j.get_to(sv);
|
||||
CHECK(json(sv) == j);
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
SECTION("get a string (implicit)")
|
||||
{
|
||||
const json::string_t s_reference{"Hello world"};
|
||||
json j(s_reference);
|
||||
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,12 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-conversions1-cpp<NN>.cpp. This file stays C++11-only.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -31,25 +30,6 @@ using nlohmann::json;
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#define JSON_HAS_CPP_14
|
||||
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
||||
#define JSON_HAS_CPP_14
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
TEST_CASE("value conversion")
|
||||
{
|
||||
SECTION("get an object (explicit)")
|
||||
@@ -519,13 +499,6 @@ TEST_CASE("value conversion")
|
||||
const std::string s = j.get<std::string>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("exception in case of a non-string type")
|
||||
{
|
||||
@@ -551,26 +524,6 @@ TEST_CASE("value conversion")
|
||||
json(json::value_t::number_float).get<json::string_t>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
}
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("exception in case of a non-string type using string_view")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("get a string (explicit, get_to)")
|
||||
@@ -591,15 +544,6 @@ TEST_CASE("value conversion")
|
||||
j.get_to(s);
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string const s = "previous value";
|
||||
std::string_view sv = s;
|
||||
j.get_to(sv);
|
||||
CHECK(json(sv) == j);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("get null (explicit)")
|
||||
@@ -638,14 +582,6 @@ TEST_CASE("value conversion")
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
SECTION("std::string_view")
|
||||
{
|
||||
std::string_view const s = j.get<std::string_view>();
|
||||
CHECK(json(s) == j);
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("std::string")
|
||||
{
|
||||
const std::string s = j;
|
||||
@@ -1256,11 +1192,4 @@ TEST_CASE("value conversion")
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#undef JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_14
|
||||
#undef JSON_HAS_CPP_14
|
||||
#endif
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -0,0 +1,225 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-conversions2.cpp (conversions of
|
||||
// std::filesystem::path, std::u8string and std::optional). It is kept in a separate
|
||||
// translation unit so the (much larger) unit-conversions2.cpp is built for C++11 only and
|
||||
// not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
TEST_CASE("std::filesystem::path")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
json const j_string = "Path";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
TEST_CASE("std::u8string")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
const std::u8string s = u8"Path";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "Path");
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
// use \u universal-character-names (rather than raw \x byte escapes
|
||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||
// literal as a nonstandard extension (warning C5321), which some of
|
||||
// our CI configs promote to an error; \u is portable and produces
|
||||
// the exact same encoded bytes without depending on the source
|
||||
// file's encoding
|
||||
const std::u8string s = u8"P\u011B\u0161ina";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// a type whose to_json reports an error by throwing, used below to check that
|
||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||
// converting its contained value instead of calling std::terminate (#5642)
|
||||
struct throwing_to_json_type {};
|
||||
|
||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||
{
|
||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("std::optional")
|
||||
{
|
||||
SECTION("null")
|
||||
{
|
||||
const json j_null;
|
||||
const std::optional<std::string> opt_null;
|
||||
|
||||
CHECK(json(opt_null) == j_null);
|
||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||
|
||||
// Constructing std::optional<T> directly from JSON null throws because
|
||||
// std::optional's own converting constructor is chosen over basic_json's
|
||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||
// constructible from T, and T is constructible from basic_json via the
|
||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||
|
||||
// Assignment goes through the same overload resolution as direct
|
||||
// construction, so it throws for the same reason. This relies on
|
||||
// basic_json's implicit conversion operator, so it only applies
|
||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
std::optional<std::string> opt_assign;
|
||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
#endif
|
||||
|
||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||
std::optional<std::string> opt_get_to = "placeholder";
|
||||
j_null.get_to(opt_get_to);
|
||||
CHECK(opt_get_to == std::nullopt);
|
||||
}
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j_string = "string";
|
||||
std::optional<std::string> opt_string = "string";
|
||||
|
||||
CHECK(json(opt_string) == j_string);
|
||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||
// @infer-ignore USE_AFTER_DELETE
|
||||
}
|
||||
|
||||
SECTION("bool")
|
||||
{
|
||||
json j_bool = true;
|
||||
std::optional<bool> opt_bool = true;
|
||||
|
||||
CHECK(json(opt_bool) == j_bool);
|
||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||
}
|
||||
|
||||
SECTION("number")
|
||||
{
|
||||
json j_number = 1;
|
||||
std::optional<int> opt_int = 1;
|
||||
|
||||
CHECK(json(opt_int) == j_number);
|
||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
json j_array = {1, 2, nullptr};
|
||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||
|
||||
CHECK(json(opt_array) == j_array);
|
||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||
|
||||
CHECK(json(opt_object) == j_object);
|
||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||
{
|
||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||
// to_json that reports an error, or std::bad_alloc for T =
|
||||
// std::string/vector/json). Before the fix, this called
|
||||
// std::terminate() instead of letting the exception propagate.
|
||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||
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
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
#endif
|
||||
@@ -6,12 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-conversions2-cpp<NN>.cpp. This file stays C++11-only.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -36,25 +35,6 @@ using nlohmann::json;
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||
#define JSON_HAS_CPP_17
|
||||
#define JSON_HAS_CPP_14
|
||||
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
||||
#define JSON_HAS_CPP_14
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
TEST_CASE("value conversion")
|
||||
{
|
||||
SECTION("get a binary value (explicit)")
|
||||
@@ -680,194 +660,4 @@ TEST_CASE("Strict JSON to enum mapping")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
TEST_CASE("std::filesystem::path")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
json const j_string = "Path";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||
json const j_path = p;
|
||||
|
||||
CHECK(j_path.template get<std::string>() ==
|
||||
j_string.template get<std::string>());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
TEST_CASE("std::u8string")
|
||||
{
|
||||
SECTION("ascii")
|
||||
{
|
||||
const std::u8string s = u8"Path";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "Path");
|
||||
}
|
||||
|
||||
SECTION("utf-8")
|
||||
{
|
||||
// use \u universal-character-names (rather than raw \x byte escapes
|
||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||
// literal as a nonstandard extension (warning C5321), which some of
|
||||
// our CI configs promote to an error; \u is portable and produces
|
||||
// the exact same encoded bytes without depending on the source
|
||||
// file's encoding
|
||||
const std::u8string s = u8"P\u011B\u0161ina";
|
||||
json const j = s;
|
||||
|
||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// a type whose to_json reports an error by throwing, used below to check that
|
||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||
// converting its contained value instead of calling std::terminate (#5642)
|
||||
struct throwing_to_json_type {};
|
||||
|
||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||
{
|
||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("std::optional")
|
||||
{
|
||||
SECTION("null")
|
||||
{
|
||||
const json j_null;
|
||||
const std::optional<std::string> opt_null;
|
||||
|
||||
CHECK(json(opt_null) == j_null);
|
||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||
|
||||
// Constructing std::optional<T> directly from JSON null throws because
|
||||
// std::optional's own converting constructor is chosen over basic_json's
|
||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||
// constructible from T, and T is constructible from basic_json via the
|
||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||
|
||||
// Assignment goes through the same overload resolution as direct
|
||||
// construction, so it throws for the same reason. This relies on
|
||||
// basic_json's implicit conversion operator, so it only applies
|
||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
std::optional<std::string> opt_assign;
|
||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||
#endif
|
||||
|
||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||
std::optional<std::string> opt_get_to = "placeholder";
|
||||
j_null.get_to(opt_get_to);
|
||||
CHECK(opt_get_to == std::nullopt);
|
||||
}
|
||||
|
||||
SECTION("string")
|
||||
{
|
||||
json j_string = "string";
|
||||
std::optional<std::string> opt_string = "string";
|
||||
|
||||
CHECK(json(opt_string) == j_string);
|
||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||
// @infer-ignore USE_AFTER_DELETE
|
||||
}
|
||||
|
||||
SECTION("bool")
|
||||
{
|
||||
json j_bool = true;
|
||||
std::optional<bool> opt_bool = true;
|
||||
|
||||
CHECK(json(opt_bool) == j_bool);
|
||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||
}
|
||||
|
||||
SECTION("number")
|
||||
{
|
||||
json j_number = 1;
|
||||
std::optional<int> opt_int = 1;
|
||||
|
||||
CHECK(json(opt_int) == j_number);
|
||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
json j_array = {1, 2, nullptr};
|
||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||
|
||||
CHECK(json(opt_array) == j_array);
|
||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||
|
||||
CHECK(json(opt_object) == j_object);
|
||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||
{
|
||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||
// to_json that reports an error, or std::bad_alloc for T =
|
||||
// std::string/vector/json). Before the fix, this called
|
||||
// std::terminate() instead of letting the exception propagate.
|
||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#undef JSON_HAS_CPP_17
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_14
|
||||
#undef JSON_HAS_CPP_14
|
||||
#endif
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -0,0 +1,58 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-custom-binary-type.cpp (binary types
|
||||
// whose value type is std::byte). It is kept in a separate translation unit so the (much
|
||||
// larger) unit-custom-binary-type.cpp is built for C++11 only and not rebuilt for every
|
||||
// C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// a BinaryType whose value type is not an integer type at all
|
||||
using byte_binary_json = nlohmann::basic_json <
|
||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
|
||||
|
||||
TEST_CASE("binary type whose value type is not std::uint8_t (C++17)")
|
||||
{
|
||||
SECTION("dumping a value type that is not an integer")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
||||
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("hashing and the binary formats")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
const auto j = byte_binary_json::binary(bytes);
|
||||
|
||||
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
||||
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
||||
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
||||
|
||||
// UBJSON has no binary type, so binary values are written as an array
|
||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||
// the same holds for BON8
|
||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -17,10 +17,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstddef>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -30,13 +26,6 @@ using char_binary_json = nlohmann::basic_json <
|
||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, std::vector<char>, void >;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// a BinaryType whose value type is not an integer type at all
|
||||
using byte_binary_json = nlohmann::basic_json <
|
||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||
double, std::allocator, nlohmann::adl_serializer, std::vector<std::byte>, void >;
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
@@ -63,29 +52,4 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
{
|
||||
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("dumping a value type that is not an integer")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
||||
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
||||
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
||||
}
|
||||
|
||||
SECTION("hashing and the binary formats")
|
||||
{
|
||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
||||
const auto j = byte_binary_json::binary(bytes);
|
||||
|
||||
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
||||
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
||||
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
||||
|
||||
// UBJSON has no binary type, so binary values are written as an array
|
||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||
// the same holds for BON8
|
||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-deserialization.cpp (char8_t support:
|
||||
// _json with char8_t literals and char8_t input). It is kept in a separate translation
|
||||
// unit so the (much larger) unit-deserialization.cpp is built for C++11 only and not
|
||||
// rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
||||
namespace
|
||||
{
|
||||
// copy of SaxEventLogger from unit-deserialization.cpp (renamed to avoid clashes in unity builds)
|
||||
struct SaxEventLoggerChar8 : public nlohmann::json_sax<json>
|
||||
{
|
||||
bool null() override
|
||||
{
|
||||
events.emplace_back("null()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool boolean(bool val) override
|
||||
{
|
||||
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t val) override
|
||||
{
|
||||
events.push_back("number_integer(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t val) override
|
||||
{
|
||||
events.push_back("number_unsigned(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*val*/, const std::string& s) override
|
||||
{
|
||||
events.push_back("number_float(" + s + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool string(std::string& val) override
|
||||
{
|
||||
events.push_back("string(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& val) override
|
||||
{
|
||||
std::string binary_contents = "binary(";
|
||||
std::string comma_space;
|
||||
for (auto b : val)
|
||||
{
|
||||
binary_contents.append(comma_space);
|
||||
binary_contents.append(std::to_string(static_cast<int>(b)));
|
||||
comma_space = ", ";
|
||||
}
|
||||
binary_contents.append(")");
|
||||
events.push_back(binary_contents);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_object()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool key(std::string& val) override
|
||||
{
|
||||
events.push_back("key(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_object() override
|
||||
{
|
||||
events.emplace_back("end_object()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_array()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_array() override
|
||||
{
|
||||
events.emplace_back("end_array()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII) (C++20)", T, char8_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||
{
|
||||
std::vector<T> const v = {'t', 'r', 'u', 'e'};
|
||||
CHECK(json::parse(v) == json(true));
|
||||
CHECK(json::accept(v));
|
||||
|
||||
SaxEventLoggerChar8 l;
|
||||
CHECK(json::sax_parse(v, &l));
|
||||
CHECK(l.events.size() == 1);
|
||||
CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("deserialization (C++20)")
|
||||
{
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json with char8_t literals #4945")
|
||||
{
|
||||
// Regular narrow string literal
|
||||
const auto j1 = R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j1["key"] == "value");
|
||||
CHECK(j1["num"] == 42);
|
||||
|
||||
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
|
||||
// \U escape rather than a raw multibyte character so this does not
|
||||
// depend on the compiler's source-file encoding (e.g., MSVC without
|
||||
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
|
||||
// string literals as UTF-8 - compare against a \x-escaped expectation
|
||||
// for the same reason)
|
||||
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
|
||||
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
|
||||
CHECK(j2["msg"] == "hello");
|
||||
|
||||
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j3["key"] == "value");
|
||||
CHECK(j3["num"] == 42);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1306,44 +1306,9 @@ TEST_CASE("deserialization")
|
||||
CHECK(line == "foo");
|
||||
}
|
||||
}
|
||||
|
||||
// build with C++20
|
||||
// JSON_HAS_CPP_20
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json with char8_t literals #4945")
|
||||
{
|
||||
// Regular narrow string literal
|
||||
const auto j1 = R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j1["key"] == "value");
|
||||
CHECK(j1["num"] == 42);
|
||||
|
||||
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
|
||||
// \U escape rather than a raw multibyte character so this does not
|
||||
// depend on the compiler's source-file encoding (e.g., MSVC without
|
||||
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
|
||||
// string literals as UTF-8 - compare against a \x-escaped expectation
|
||||
// for the same reason)
|
||||
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
|
||||
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
|
||||
CHECK(j2["msg"] == "hello");
|
||||
|
||||
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
|
||||
CHECK(j3["key"] == "value");
|
||||
CHECK(j3["num"] == 42);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// select the types to test - char8_t is only available since C++20 if and only
|
||||
// if __cpp_char8_t is defined.
|
||||
#define TYPE_LIST(...) __VA_ARGS__
|
||||
#if defined(__cpp_char8_t) && (__cpp_char8_t >= 201811L)
|
||||
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t, char8_t)
|
||||
#else
|
||||
#define ASCII_TYPES TYPE_LIST(char, wchar_t, char16_t, char32_t)
|
||||
#endif
|
||||
|
||||
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, ASCII_TYPES) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||
TEST_CASE_TEMPLATE("deserialization of different character types (ASCII)", T, char, wchar_t, char16_t, char32_t) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||
{
|
||||
std::vector<T> const v = {'t', 'r', 'u', 'e'};
|
||||
CHECK(json::parse(v) == json(true));
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -27,6 +27,9 @@ using nlohmann::json;
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
// the types live in a namespace, because the file may be compiled together with other test files
|
||||
namespace enum_keyed_maps_default
|
||||
{
|
||||
enum class cards {kreuz, pik, herz, karo};
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
||||
@@ -65,6 +68,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
||||
{strict_cards::herz, "herz"},
|
||||
{strict_cards::karo, "karo"}
|
||||
})
|
||||
} // namespace enum_keyed_maps_default
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -82,6 +86,15 @@ struct enum_hash
|
||||
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
|
||||
TEST_CASE("maps with enum keys")
|
||||
{
|
||||
// block-scope using-declarations take precedence over same-named types of other files in the same translation unit
|
||||
using enum_keyed_maps_default::cards;
|
||||
using enum_keyed_maps_default::strict_cards;
|
||||
using enum_keyed_maps_default::TaskState;
|
||||
using enum_keyed_maps_default::TS_COMPLETED;
|
||||
using enum_keyed_maps_default::TS_INVALID;
|
||||
using enum_keyed_maps_default::TS_RUNNING;
|
||||
using enum_keyed_maps_default::TS_STOPPED;
|
||||
|
||||
using task_map = std::map<TaskState, std::string>;
|
||||
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
|
||||
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>;
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// The regression below only showed on C++17, so build this file for every
|
||||
// standard like the other regression tests:
|
||||
// JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. The regression below only
|
||||
// showed on C++17, and the whole file is just this explicit instantiation, so it
|
||||
// cannot be split: build this file for every standard like the other regression tests:
|
||||
// JSON_HAS_CPP_17 JSON_HAS_CPP_20 (do not remove)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-iterators2.cpp (iterators and
|
||||
// std::ranges). It is kept in a separate translation unit so the (much larger) unit-
|
||||
// iterators2.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
TEST_CASE("iterators 2 (C++20)")
|
||||
{
|
||||
#if JSON_HAS_RANGES
|
||||
SECTION("ranges")
|
||||
{
|
||||
SECTION("concepts")
|
||||
{
|
||||
using nlohmann::detail::iteration_proxy_value;
|
||||
CHECK(std::bidirectional_iterator<json::iterator>);
|
||||
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
|
||||
|
||||
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
|
||||
CHECK(std::ranges::bidirectional_range<json>);
|
||||
|
||||
using nlohmann::detail::iteration_proxy;
|
||||
using items_type = decltype(std::declval<json&>().items());
|
||||
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
|
||||
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
|
||||
CHECK(std::ranges::input_range<items_type>);
|
||||
}
|
||||
|
||||
SECTION("algorithms")
|
||||
{
|
||||
SECTION("copy")
|
||||
{
|
||||
json j{"foo", "bar"};
|
||||
auto j_copied = json::array();
|
||||
|
||||
std::ranges::copy(j, std::back_inserter(j_copied));
|
||||
|
||||
CHECK(j == j_copied);
|
||||
}
|
||||
|
||||
SECTION("find_if")
|
||||
{
|
||||
json j{1, 3, 2, 4};
|
||||
auto j_even = json::array();
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
auto it = std::ranges::find_if(j, [](int v) noexcept
|
||||
{
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#else
|
||||
auto it = std::ranges::find_if(j, [](const json & j) noexcept
|
||||
{
|
||||
int v;
|
||||
j.get_to(v);
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
CHECK(*it == 2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("views")
|
||||
{
|
||||
SECTION("reverse")
|
||||
{
|
||||
json j{1, 2, 3, 4, 5};
|
||||
json j_expected{5, 4, 3, 2, 1};
|
||||
|
||||
auto reversed = j | std::views::reverse;
|
||||
CHECK(reversed == j_expected);
|
||||
}
|
||||
|
||||
SECTION("transform")
|
||||
{
|
||||
json j
|
||||
{
|
||||
{ "a_key", "a_value"},
|
||||
{ "b_key", "b_value"},
|
||||
{ "c_key", "c_value"},
|
||||
};
|
||||
json j_expected{"a_key", "b_key", "c_key"};
|
||||
|
||||
// NOLINTNEXTLINE(fuchsia-trailing-return)
|
||||
auto transformed = j.items() | std::views::transform([](const auto & item) -> std::string_view
|
||||
{
|
||||
return item.key();
|
||||
});
|
||||
auto j_transformed = json::array();
|
||||
std::ranges::copy(transformed, std::back_inserter(j_transformed));
|
||||
|
||||
CHECK(j_transformed == j_expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,23 +6,17 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-iterators2-cpp<NN>.cpp. This file stays C++11-only.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
TEST_CASE("iterators 2")
|
||||
{
|
||||
SECTION("iterator comparisons")
|
||||
@@ -872,94 +866,4 @@ TEST_CASE("iterators 2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_HAS_RANGES
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
SECTION("ranges")
|
||||
{
|
||||
SECTION("concepts")
|
||||
{
|
||||
using nlohmann::detail::iteration_proxy_value;
|
||||
CHECK(std::bidirectional_iterator<json::iterator>);
|
||||
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
|
||||
|
||||
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
|
||||
CHECK(std::ranges::bidirectional_range<json>);
|
||||
|
||||
using nlohmann::detail::iteration_proxy;
|
||||
using items_type = decltype(std::declval<json&>().items());
|
||||
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
|
||||
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
|
||||
CHECK(std::ranges::input_range<items_type>);
|
||||
}
|
||||
|
||||
SECTION("algorithms")
|
||||
{
|
||||
SECTION("copy")
|
||||
{
|
||||
json j{"foo", "bar"};
|
||||
auto j_copied = json::array();
|
||||
|
||||
std::ranges::copy(j, std::back_inserter(j_copied));
|
||||
|
||||
CHECK(j == j_copied);
|
||||
}
|
||||
|
||||
SECTION("find_if")
|
||||
{
|
||||
json j{1, 3, 2, 4};
|
||||
auto j_even = json::array();
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
auto it = std::ranges::find_if(j, [](int v) noexcept
|
||||
{
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#else
|
||||
auto it = std::ranges::find_if(j, [](const json & j) noexcept
|
||||
{
|
||||
int v;
|
||||
j.get_to(v);
|
||||
return (v % 2) == 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
CHECK(*it == 2);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("views")
|
||||
{
|
||||
SECTION("reverse")
|
||||
{
|
||||
json j{1, 2, 3, 4, 5};
|
||||
json j_expected{5, 4, 3, 2, 1};
|
||||
|
||||
auto reversed = j | std::views::reverse;
|
||||
CHECK(reversed == j_expected);
|
||||
}
|
||||
|
||||
SECTION("transform")
|
||||
{
|
||||
json j
|
||||
{
|
||||
{ "a_key", "a_value"},
|
||||
{ "b_key", "b_value"},
|
||||
{ "c_key", "c_value"},
|
||||
};
|
||||
json j_expected{"a_key", "b_key", "c_key"};
|
||||
|
||||
// NOLINTNEXTLINE(fuchsia-trailing-return)
|
||||
auto transformed = j.items() | std::views::transform([](const auto & item) -> std::string_view
|
||||
{
|
||||
return item.key();
|
||||
});
|
||||
auto j_transformed = json::array();
|
||||
std::ranges::copy(transformed, std::back_inserter(j_transformed));
|
||||
|
||||
CHECK(j_transformed == j_expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-json_pointer.cpp (JSON pointer comparison
|
||||
// with C++20 rewritten candidates and operator<=>, and char8_t literals). It is kept in a
|
||||
// separate translation unit so the (much larger) unit-json_pointer.cpp is built for C++11
|
||||
// only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
||||
// line *before* including json.hpp, since the library #undefs it once the header
|
||||
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
||||
// tests of deprecated functions are skipped if these functions are deleted
|
||||
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
||||
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <compare>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
TEST_CASE("JSON pointers (C++20)")
|
||||
{
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
std::string ptr_string{"/foo/bar"};
|
||||
auto ptr1 = json::json_pointer(ptr_string);
|
||||
auto ptr2 = json::json_pointer(ptr_string);
|
||||
|
||||
CHECK(ptr1 == ptr2);
|
||||
|
||||
CHECK_FALSE(ptr1 != ptr2);
|
||||
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
const char* ptr_cpstring = "/foo/bar";
|
||||
const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
|
||||
|
||||
CHECK(ptr1 == "/foo/bar");
|
||||
CHECK(ptr1 == ptr_cpstring);
|
||||
CHECK(ptr1 == ptr_castring);
|
||||
CHECK(ptr1 == ptr_string);
|
||||
|
||||
CHECK("/foo/bar" == ptr1);
|
||||
CHECK(ptr_cpstring == ptr1);
|
||||
CHECK(ptr_castring == ptr1);
|
||||
CHECK(ptr_string == ptr1);
|
||||
|
||||
CHECK_FALSE(ptr1 != "/foo/bar");
|
||||
CHECK_FALSE(ptr1 != ptr_cpstring);
|
||||
CHECK_FALSE(ptr1 != ptr_castring);
|
||||
CHECK_FALSE(ptr1 != ptr_string);
|
||||
|
||||
CHECK_FALSE("/foo/bar" != ptr1);
|
||||
CHECK_FALSE(ptr_cpstring != ptr1);
|
||||
CHECK_FALSE(ptr_castring != ptr1);
|
||||
CHECK_FALSE(ptr_string != ptr1);
|
||||
|
||||
SECTION("exceptions")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(ptr1 == "foo",
|
||||
"[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS("foo" == ptr1,
|
||||
"[json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'foo'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(ptr1 == "/~~",
|
||||
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS("/~~" == ptr1,
|
||||
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("less-than comparison")
|
||||
{
|
||||
auto ptr1 = json::json_pointer("/foo/a");
|
||||
auto ptr2 = json::json_pointer("/foo/b");
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
|
||||
CHECK(ptr2 > ptr1);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("backwards compatibility and mixing")
|
||||
{
|
||||
json j = R"(
|
||||
{
|
||||
"foo": ["bar", "baz"]
|
||||
}
|
||||
)"_json;
|
||||
|
||||
using nlohmann::ordered_json;
|
||||
using json_ptr_str = nlohmann::json_pointer<std::string>;
|
||||
using json_ptr_j = nlohmann::json_pointer<json>;
|
||||
using json_ptr_oj = nlohmann::json_pointer<ordered_json>;
|
||||
|
||||
std::string const ptr_string{"/foo/0"};
|
||||
json_ptr_str ptr{ptr_string};
|
||||
json_ptr_j ptr_j{ptr_string};
|
||||
json_ptr_oj ptr_oj{ptr_string};
|
||||
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
CHECK(ptr == ptr_j);
|
||||
CHECK(ptr == ptr_oj);
|
||||
CHECK(ptr_j == ptr);
|
||||
CHECK(ptr_j == ptr_oj);
|
||||
CHECK(ptr_oj == ptr_j);
|
||||
CHECK(ptr_oj == ptr);
|
||||
|
||||
CHECK_FALSE(ptr != ptr_j);
|
||||
CHECK_FALSE(ptr != ptr_oj);
|
||||
CHECK_FALSE(ptr_j != ptr);
|
||||
CHECK_FALSE(ptr_j != ptr_oj);
|
||||
CHECK_FALSE(ptr_oj != ptr_j);
|
||||
CHECK_FALSE(ptr_oj != ptr);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json_pointer with char8_t literals #4945")
|
||||
{
|
||||
const json j = R"({"a": {"b": {"c": 123}}})"_json;
|
||||
const auto p1 = "/a/b/c"_json_pointer;
|
||||
CHECK(j[p1] == 123);
|
||||
|
||||
const auto p2 = u8"/a/b/c"_json_pointer;
|
||||
CHECK(j[p2] == 123);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -752,14 +752,13 @@ TEST_CASE("JSON pointers")
|
||||
CHECK(ptr.to_string() == "/object/~1");
|
||||
}
|
||||
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON // with three-way comparison, see unit-json_pointer-cpp20.cpp
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
std::string ptr_string{"/foo/bar"};
|
||||
auto ptr1 = json::json_pointer(ptr_string);
|
||||
auto ptr2 = json::json_pointer(ptr_string);
|
||||
|
||||
// build with C++20 to test rewritten candidates
|
||||
// JSON_HAS_CPP_20
|
||||
|
||||
CHECK(ptr1 == ptr2);
|
||||
|
||||
@@ -802,6 +801,7 @@ TEST_CASE("JSON pointers")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("less-than comparison")
|
||||
{
|
||||
@@ -811,12 +811,6 @@ TEST_CASE("JSON pointers")
|
||||
CHECK(ptr1 < ptr2);
|
||||
CHECK_FALSE(ptr2 < ptr1);
|
||||
|
||||
// build with C++20
|
||||
// JSON_HAS_CPP_20
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((ptr1 <=> ptr2) == std::strong_ordering::less); // *NOPAD*
|
||||
CHECK(ptr2 > ptr1);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("usable as map key")
|
||||
@@ -872,10 +866,9 @@ TEST_CASE("JSON pointers")
|
||||
CHECK_FALSE(ptr != ptr_j);
|
||||
CHECK_FALSE(ptr != ptr_oj);
|
||||
|
||||
#if !JSON_HAS_THREE_WAY_COMPARISON // with three-way comparison, see unit-json_pointer-cpp20.cpp
|
||||
SECTION("equality comparison")
|
||||
{
|
||||
// build with C++20 to test rewritten candidates
|
||||
// JSON_HAS_CPP_20
|
||||
|
||||
CHECK(ptr == ptr_j);
|
||||
CHECK(ptr == ptr_oj);
|
||||
@@ -891,6 +884,7 @@ TEST_CASE("JSON pointers")
|
||||
CHECK_FALSE(ptr_oj != ptr_j);
|
||||
CHECK_FALSE(ptr_oj != ptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("value(json_pointer, default) with ordered_json #5664")
|
||||
@@ -912,19 +906,6 @@ TEST_CASE("JSON pointers")
|
||||
CHECK(j.value(ptr_missing, 42) == 42);
|
||||
}
|
||||
|
||||
// build with C++20
|
||||
// JSON_HAS_CPP_20
|
||||
#if defined(__cpp_char8_t)
|
||||
SECTION("Using _json_pointer with char8_t literals #4945")
|
||||
{
|
||||
const json j = R"({"a": {"b": {"c": 123}}})"_json;
|
||||
const auto p1 = "/a/b/c"_json_pointer;
|
||||
CHECK(j[p1] == 123);
|
||||
|
||||
const auto p2 = u8"/a/b/c"_json_pointer;
|
||||
CHECK(j[p2] == 123);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
|
||||
|
||||
@@ -46,7 +46,7 @@ void reference_merge_patch(json& target, const json& patch)
|
||||
|
||||
// objects nested `depth` levels deep under the key "a", with members that
|
||||
// differ by `variant` on the way down
|
||||
std::string nested_objects(const std::size_t depth, const int variant)
|
||||
std::string nested_objects_merge_patch(const std::size_t depth, const int variant)
|
||||
{
|
||||
std::string text;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
@@ -309,9 +309,9 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
||||
for (int variant = 0; variant < 3; ++variant)
|
||||
{
|
||||
CAPTURE(variant)
|
||||
const json patch = json::parse(nested_objects(depth, variant));
|
||||
const json patch = json::parse(nested_objects_merge_patch(depth, variant));
|
||||
|
||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||
json result = json::parse(nested_objects_merge_patch(depth, (variant + 1) % 3));
|
||||
json expected = result;
|
||||
result.merge_patch(patch);
|
||||
reference_merge_patch(expected, patch);
|
||||
@@ -332,8 +332,8 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
|
||||
// applying a patch used to recurse once per nesting level. The result
|
||||
// is only walked, never copied or compared, since those recurse too.
|
||||
const std::size_t depth = 100000;
|
||||
json target = json::parse(nested_objects(depth, 0));
|
||||
target.merge_patch(json::parse(nested_objects(depth, 1)));
|
||||
json target = json::parse(nested_objects_merge_patch(depth, 0));
|
||||
target.merge_patch(json::parse(nested_objects_merge_patch(depth, 1)));
|
||||
|
||||
const json* p = ⌖
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
@@ -404,7 +404,7 @@ TEST_CASE("merge_patch() with an argument that aliases *this (#5641)")
|
||||
})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
json j = json::parse(nested_objects(depth, 0));
|
||||
json j = json::parse(nested_objects_merge_patch(depth, 0));
|
||||
const json expected = j;
|
||||
j.merge_patch(j);
|
||||
CHECK(j == expected);
|
||||
|
||||
@@ -35,7 +35,7 @@ void reference_update(json& target, const json& source)
|
||||
|
||||
// objects nested `depth` levels deep under the key "a", with members that
|
||||
// differ by `variant` on the way down
|
||||
std::string nested_objects(const std::size_t depth, const int variant)
|
||||
std::string nested_objects_update(const std::size_t depth, const int variant)
|
||||
{
|
||||
std::string text;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
@@ -1131,8 +1131,8 @@ TEST_CASE("update() on deeply nested values")
|
||||
for (int variant = 0; variant < 3; ++variant)
|
||||
{
|
||||
CAPTURE(variant)
|
||||
const json source = json::parse(nested_objects(depth, variant));
|
||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||
const json source = json::parse(nested_objects_update(depth, variant));
|
||||
json result = json::parse(nested_objects_update(depth, (variant + 1) % 3));
|
||||
json expected = result;
|
||||
result.update(source, true);
|
||||
reference_update(expected, source);
|
||||
@@ -1146,8 +1146,8 @@ TEST_CASE("update() on deeply nested values")
|
||||
// merging used to recurse once per nesting level. The result is only
|
||||
// walked, never copied or compared, since those recurse too.
|
||||
const std::size_t depth = 100000;
|
||||
json target = json::parse(nested_objects(depth, 0));
|
||||
target.update(json::parse(nested_objects(depth, 1)), true);
|
||||
json target = json::parse(nested_objects_update(depth, 0));
|
||||
target.update(json::parse(nested_objects_update(depth, 1)), true);
|
||||
|
||||
const json* p = ⌖
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
@@ -1223,7 +1223,7 @@ TEST_CASE("update() with an argument that aliases *this (#5641)")
|
||||
})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
json j = json::parse(nested_objects(depth, 0));
|
||||
json j = json::parse(nested_objects_update(depth, 0));
|
||||
const json expected = j;
|
||||
j.update(j, true);
|
||||
CHECK(j == expected);
|
||||
|
||||
+30
-30
@@ -2285,7 +2285,7 @@ TEST_CASE("MessagePack Size above uint32 for object")
|
||||
}
|
||||
|
||||
#ifdef JSON_TEST_BEYOND_UINT32_STRING
|
||||
struct huge_string : std::string
|
||||
struct msgpack_huge_string : std::string
|
||||
{
|
||||
using std::string::string;
|
||||
|
||||
@@ -2295,31 +2295,31 @@ struct huge_string : std::string
|
||||
}
|
||||
};
|
||||
|
||||
using huge_string_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
huge_string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
void >;
|
||||
using msgpack_huge_string_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
msgpack_huge_string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for string")
|
||||
{
|
||||
const huge_string_json j = "hello";
|
||||
const msgpack_huge_string_json j = "hello";
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_string_json::to_msgpack(j),
|
||||
msgpack_huge_string_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct huge_binary : std::vector<std::uint8_t>
|
||||
struct msgpack_huge_binary : std::vector<std::uint8_t>
|
||||
{
|
||||
using std::vector<std::uint8_t>::vector;
|
||||
|
||||
@@ -2329,29 +2329,29 @@ struct huge_binary : std::vector<std::uint8_t>
|
||||
}
|
||||
};
|
||||
|
||||
using huge_binary_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
huge_binary,
|
||||
void >;
|
||||
using msgpack_huge_binary_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
msgpack_huge_binary,
|
||||
void >;
|
||||
|
||||
TEST_CASE("MessagePack Size above uint32 for binary")
|
||||
{
|
||||
|
||||
huge_binary_json j = huge_binary_json::binary(huge_binary{});
|
||||
msgpack_huge_binary_json j = msgpack_huge_binary_json::binary(msgpack_huge_binary{});
|
||||
|
||||
j.get_binary().push_back(0x01);
|
||||
j.get_binary().push_back(0x02);
|
||||
|
||||
CHECK_THROWS_WITH_AS(
|
||||
huge_binary_json::to_msgpack(j),
|
||||
msgpack_huge_binary_json::to_msgpack(j),
|
||||
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
|
||||
json::out_of_range&);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ using nlohmann::ordered_json;
|
||||
// this type, which is presumably why the gap was never noticed.
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace unit_ordered_json2_detail
|
||||
{
|
||||
class alt_string;
|
||||
bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage)
|
||||
void int_to_string(alt_string& target, std::size_t value); // NOLINT(misc-use-internal-linkage)
|
||||
@@ -217,21 +219,24 @@ void int_to_string(alt_string& target, std::size_t value)
|
||||
target = std::to_string(value).c_str();
|
||||
}
|
||||
|
||||
using alt_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
alt_string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer >;
|
||||
using ordered_json2_alt_json = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
alt_string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer >;
|
||||
|
||||
bool operator<(const char* op1, const alt_string& op2) noexcept
|
||||
{
|
||||
return op1 < op2.str_impl;
|
||||
}
|
||||
} // namespace unit_ordered_json2_detail
|
||||
|
||||
using unit_ordered_json2_detail::ordered_json2_alt_json;
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -262,11 +267,11 @@ ordered_json make_rich_ordered_json()
|
||||
return j;
|
||||
}
|
||||
|
||||
alt_json make_rich_alt_json()
|
||||
ordered_json2_alt_json make_rich_alt_json()
|
||||
{
|
||||
alt_json j;
|
||||
ordered_json2_alt_json j;
|
||||
j["zebra"] = 1;
|
||||
j["apple"] = alt_json::array({1, 2, 3});
|
||||
j["apple"] = ordered_json2_alt_json::array({1, 2, 3});
|
||||
j["mango"]["z_nested"] = true;
|
||||
j["mango"]["a_nested"] = nullptr;
|
||||
j["banana"] = "some text";
|
||||
@@ -339,47 +344,47 @@ TEST_CASE("ordered_json across binary formats")
|
||||
|
||||
TEST_CASE("alt_json (custom string_t) across binary formats")
|
||||
{
|
||||
const alt_json original = make_rich_alt_json();
|
||||
const ordered_json2_alt_json original = make_rich_alt_json();
|
||||
|
||||
SECTION("CBOR")
|
||||
{
|
||||
const auto bytes = alt_json::to_cbor(original);
|
||||
const auto restored = alt_json::from_cbor(bytes);
|
||||
const auto bytes = ordered_json2_alt_json::to_cbor(original);
|
||||
const auto restored = ordered_json2_alt_json::from_cbor(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("MessagePack")
|
||||
{
|
||||
const auto bytes = alt_json::to_msgpack(original);
|
||||
const auto restored = alt_json::from_msgpack(bytes);
|
||||
const auto bytes = ordered_json2_alt_json::to_msgpack(original);
|
||||
const auto restored = ordered_json2_alt_json::from_msgpack(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("UBJSON")
|
||||
{
|
||||
const auto bytes = alt_json::to_ubjson(original);
|
||||
const auto restored = alt_json::from_ubjson(bytes);
|
||||
const auto bytes = ordered_json2_alt_json::to_ubjson(original);
|
||||
const auto restored = ordered_json2_alt_json::from_ubjson(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("BON8")
|
||||
{
|
||||
const auto bytes = alt_json::to_bon8(original);
|
||||
const auto restored = alt_json::from_bon8(bytes);
|
||||
const auto bytes = ordered_json2_alt_json::to_bon8(original);
|
||||
const auto restored = ordered_json2_alt_json::from_bon8(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("BSON")
|
||||
{
|
||||
const auto bytes = alt_json::to_bson(original);
|
||||
const auto restored = alt_json::from_bson(bytes);
|
||||
const auto bytes = ordered_json2_alt_json::to_bson(original);
|
||||
const auto restored = ordered_json2_alt_json::from_bson(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("BJData")
|
||||
{
|
||||
const auto bytes = alt_json::to_bjdata(original);
|
||||
const auto restored = alt_json::from_bjdata(bytes);
|
||||
const auto bytes = ordered_json2_alt_json::to_bjdata(original);
|
||||
const auto restored = ordered_json2_alt_json::from_bjdata(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-ordered_map.cpp (ordered_map::find with
|
||||
// std::string_view keys). It is kept in a separate translation unit so the (much larger)
|
||||
// unit-ordered_map.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::ordered_map;
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
TEST_CASE("ordered_map (C++17)")
|
||||
{
|
||||
SECTION("find")
|
||||
{
|
||||
ordered_map<std::string, std::string> om;
|
||||
om["eins"] = "one";
|
||||
om["zwei"] = "two";
|
||||
om["drei"] = "three";
|
||||
const auto com = om;
|
||||
|
||||
const std::string eins("eins");
|
||||
const std::string vier("vier");
|
||||
|
||||
CHECK(om.find(std::string_view("eins")) == om.begin());
|
||||
CHECK(com.find(std::string_view("eins")) == com.begin());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -360,11 +360,6 @@ TEST_CASE("ordered_map")
|
||||
CHECK(com.find("vier") == com.end());
|
||||
CHECK(com.find(std::string("vier")) == com.end());
|
||||
CHECK(com.find(vier) == com.end());
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
CHECK(om.find(std::string_view("eins")) == om.begin());
|
||||
CHECK(com.find(std::string_view("eins")) == com.begin());
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("insert")
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-regression2.cpp (std::variant, std::any
|
||||
// and std::optional regression tests). It is kept in a separate translation unit so the
|
||||
// (much larger) unit-regression2.cpp is built for C++11 only and not rebuilt for every C++
|
||||
// standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <any>
|
||||
#include <variant>
|
||||
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4804
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||
|
||||
TEST_CASE("regression tests 2 (C++17)")
|
||||
{
|
||||
SECTION("issue #1292 - Serializing std::variant causes stack overflow")
|
||||
{
|
||||
static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
|
||||
}
|
||||
|
||||
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
|
||||
{
|
||||
// std::variant<json> must not be retrievable via get<>(), because otherwise the
|
||||
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
|
||||
// converting constructor, routing a number through the string from_json overload
|
||||
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
|
||||
"std::variant<json> must not be retrievable via get<>()");
|
||||
|
||||
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
|
||||
#if !(defined(__clang__) && __clang_major__ < 7)
|
||||
// push_back, not emplace_back: #5066 needs the implicit conversion
|
||||
// from json to the vector's value type
|
||||
std::vector<std::variant<json>> v;
|
||||
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
|
||||
CHECK(std::get<0>(v[0]) == 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-regression2.cpp (std::span and ranges
|
||||
// regression tests). It is kept in a separate translation unit so the (much larger) unit-
|
||||
// regression2.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <string>
|
||||
|
||||
#if __has_include(<span>)
|
||||
#include <span>
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4440
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if JSON_HAS_RANGES == 1
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
TEST_CASE("regression tests 2 (C++20)")
|
||||
{
|
||||
#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
|
||||
// classic Intel ICC reports <span> as includable but cannot actually compile
|
||||
// std::span/std::as_bytes usage below
|
||||
#if __has_include(<span>) && !defined(__ICC) && !defined(__INTEL_COMPILER)
|
||||
SECTION("issue #2546 - parsing containers of std::byte")
|
||||
{
|
||||
const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
// exclude the trailing '\0' that string-literal initialization adds to
|
||||
// DATA: std::span(DATA) would span the full array extent (including
|
||||
// that NUL), which is only silently accepted as end-of-input by default
|
||||
// and would fail under JSON_STRICT_NUL_HANDLING
|
||||
const auto s = std::as_bytes(std::span(DATA, sizeof(DATA) - 1));
|
||||
const json j = json::parse(s);
|
||||
CHECK(j.dump() == "\"Hello, world!\"");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,12 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-regression2-cpp<NN>.cpp. This file stays C++11-only.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -49,30 +48,6 @@ using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include "test_utils.hpp"
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <any>
|
||||
#include <variant>
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#include <experimental/optional>
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4804
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#if __has_include(<span>)
|
||||
#include <span>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4825 - explicitly instantiating basic_json must compile; this
|
||||
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
|
||||
@@ -81,13 +56,6 @@ using ordered_json = nlohmann::ordered_json;
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
template class nlohmann::basic_json<>;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4440
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if JSON_HAS_RANGES == 1
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
@@ -285,18 +253,18 @@ struct adl_serializer<NonDefaultConstructible>
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<class T>
|
||||
class my_allocator : public std::allocator<T>
|
||||
class my_allocator_2982 : public std::allocator<T>
|
||||
{
|
||||
public:
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
my_allocator() = default;
|
||||
template<class U> my_allocator(const my_allocator<U>& /*unused*/) { }
|
||||
my_allocator_2982() = default;
|
||||
template<class U> my_allocator_2982(const my_allocator_2982<U>& /*unused*/) { }
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = my_allocator<U>;
|
||||
using other = my_allocator_2982<U>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -468,13 +436,6 @@ TEST_CASE("regression tests 2")
|
||||
CHECK(diffs.size() == 1); // Note the change here, was 2
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("issue #1292 - Serializing std::variant causes stack overflow")
|
||||
{
|
||||
static_assert(!std::is_constructible<json, std::variant<int, float>>::value, "unexpected value");
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #1299 - compile error in from_json converting to container "
|
||||
"with std::pair")
|
||||
{
|
||||
@@ -718,26 +679,6 @@ TEST_CASE("regression tests 2")
|
||||
CHECK(j.dump() == "{}");
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#ifndef _LIBCPP_VERSION // see https://github.com/nlohmann/json/issues/4490
|
||||
// classic Intel ICC reports <span> as includable but cannot actually compile
|
||||
// std::span/std::as_bytes usage below
|
||||
#if __has_include(<span>) && !defined(__ICC) && !defined(__INTEL_COMPILER)
|
||||
SECTION("issue #2546 - parsing containers of std::byte")
|
||||
{
|
||||
const char DATA[] = R"("Hello, world!")"; // NOLINT(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
// exclude the trailing '\0' that string-literal initialization adds to
|
||||
// DATA: std::span(DATA) would span the full array extent (including
|
||||
// that NUL), which is only silently accepted as end-of-input by default
|
||||
// and would fail under JSON_STRICT_NUL_HANDLING
|
||||
const auto s = std::as_bytes(std::span(DATA, sizeof(DATA) - 1));
|
||||
const json j = json::parse(s);
|
||||
CHECK(j.dump() == "\"Hello, world!\"");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SECTION("issue #2574 - Deserialization to std::array, std::pair, and std::tuple with non-default constructable types fails")
|
||||
{
|
||||
SECTION("std::array")
|
||||
@@ -868,7 +809,7 @@ TEST_CASE("regression tests 2")
|
||||
|
||||
SECTION("issue #2982 - to_{binary format} does not provide a mechanism for specifying a custom allocator for the returned type")
|
||||
{
|
||||
std::vector<std::uint8_t, my_allocator<std::uint8_t>> my_vector;
|
||||
std::vector<std::uint8_t, my_allocator_2982<std::uint8_t>> my_vector;
|
||||
const json j = {1, 2, 3, 4};
|
||||
json::to_cbor(j, my_vector);
|
||||
json k = json::from_cbor(my_vector);
|
||||
@@ -884,26 +825,6 @@ TEST_CASE("regression tests 2")
|
||||
CHECK(node.dump(-1, ' ', true, json::error_handler_t::keep) == "{\"test\":\"test\334\\u0005\"}");
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("issue #5066 - MSVC converts json to std::variant<json> via the conversion operator")
|
||||
{
|
||||
// std::variant<json> must not be retrievable via get<>(), because otherwise the
|
||||
// implicit conversion operator becomes a candidate that MSVC picks over the variant's
|
||||
// converting constructor, routing a number through the string from_json overload
|
||||
static_assert(!nlohmann::detail::is_detected<nlohmann::detail::get_template_function, const json&, std::variant<json>>::value,
|
||||
"std::variant<json> must not be retrievable via get<>()");
|
||||
|
||||
// clang before 7 cannot instantiate libstdc++'s std::variant<json>
|
||||
#if !(defined(__clang__) && __clang_major__ < 7)
|
||||
// push_back, not emplace_back: #5066 needs the implicit conversion
|
||||
// from json to the vector's value type
|
||||
std::vector<std::variant<json>> v;
|
||||
v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace)
|
||||
CHECK(std::get<0>(v[0]) == 1);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json")
|
||||
{
|
||||
const Issue3669Holder h{};
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++17-only part of unit-regression3.cpp (std::any, std::optional,
|
||||
// std::variant and std::filesystem regression tests). It is kept in a separate translation
|
||||
// unit so the (much larger) unit-regression3.cpp is built for C++11 only and not rebuilt
|
||||
// for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// skip tests if JSON_DisableEnumSerialization=ON (#4384): std::byte is a
|
||||
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
||||
// from a plain JSON array, not just from an already-binary value) relies on
|
||||
// enum serialization being enabled
|
||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
#include <any>
|
||||
#include <variant>
|
||||
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4804
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4740
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct Example_4740
|
||||
{
|
||||
std::optional<std::string> host = std::nullopt;
|
||||
std::optional<int> port = std::nullopt;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
|
||||
};
|
||||
|
||||
TEST_CASE("regression tests 3 (C++17)")
|
||||
{
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
|
||||
{
|
||||
nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
|
||||
const json j(text_path);
|
||||
|
||||
const auto j_path = j.get<nlohmann::detail::std_fs::path>();
|
||||
CHECK(j_path == text_path);
|
||||
|
||||
#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
|
||||
// only known to work on Clang and GCC >=8.4
|
||||
CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||
SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
|
||||
{
|
||||
const json j;
|
||||
const std::any a1 = j;
|
||||
std::any&& a2 = j;
|
||||
|
||||
CHECK(a1.type() == typeid(j));
|
||||
CHECK(a2.type() == typeid(j));
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #4740 - build issue with std::optional")
|
||||
{
|
||||
const auto t1 = Example_4740();
|
||||
const auto j1 = nlohmann::json(t1);
|
||||
CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
|
||||
const auto t2 = j1.get<Example_4740>();
|
||||
CHECK(!t2.host.has_value());
|
||||
CHECK(!t2.port.has_value());
|
||||
|
||||
// improve coverage
|
||||
auto t3 = Example_4740();
|
||||
t3.port = 80;
|
||||
t3.host = "example.com";
|
||||
const auto j2 = nlohmann::json(t3);
|
||||
CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
|
||||
const auto t4 = j2.get<Example_4740>();
|
||||
CHECK(t4.host.has_value());
|
||||
CHECK(t4.port.has_value());
|
||||
}
|
||||
|
||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||||
{
|
||||
const std::vector<std::uint8_t> data = {0x80};
|
||||
const auto decoded = json_4804::from_cbor(data);
|
||||
CHECK((decoded == json_4804::array()));
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||
{
|
||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||
const json_4804 j = original;
|
||||
CHECK(j.is_binary());
|
||||
CHECK(!j.is_array());
|
||||
|
||||
// Test round-tripping: extracting the binary value back as the custom container type
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted == original);
|
||||
|
||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||
CHECK(default_json.is_array());
|
||||
CHECK(!default_json.is_binary());
|
||||
}
|
||||
|
||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||
{
|
||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||
// (not just from a binary-typed node)
|
||||
const auto j = json_4804::parse("[1,2,3]");
|
||||
CHECK(j.is_array());
|
||||
CHECK(!j.is_binary());
|
||||
|
||||
// Extracting as custom BinaryType should work from arrays
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted.size() == 3);
|
||||
CHECK(extracted[0] == std::byte{1});
|
||||
CHECK(extracted[1] == std::byte{2});
|
||||
CHECK(extracted[2] == std::byte{3});
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
||||
{
|
||||
const json jval{};
|
||||
auto GetValue = [](const json & valRoot) -> std::optional<json>
|
||||
{
|
||||
if (valRoot.contains("default"))
|
||||
{
|
||||
return valRoot.at("default");
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
auto result = GetValue(jval);
|
||||
CHECK(!result.has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,127 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-regression3.cpp (C++20 regression tests
|
||||
// (operator<=> related aggregates, ranges)). It is kept in a separate translation unit so
|
||||
// the (much larger) unit-regression3.cpp is built for C++11 only and not rebuilt for every
|
||||
// C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// skip tests if JSON_DisableEnumSerialization=ON (#4384): std::byte is a
|
||||
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
||||
// from a plain JSON array, not just from an already-binary value) relies on
|
||||
// enum serialization being enabled
|
||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <string>
|
||||
|
||||
#if __has_include(<span>)
|
||||
#include <span>
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4440
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if JSON_HAS_RANGES == 1
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #3312
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct for_3312
|
||||
{
|
||||
std::string name;
|
||||
};
|
||||
|
||||
inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
|
||||
{
|
||||
j.at("name").get_to(obj.name);
|
||||
}
|
||||
|
||||
TEST_CASE("regression tests 3 (C++20)")
|
||||
{
|
||||
SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
|
||||
{
|
||||
// see test for #3171
|
||||
const ordered_json j = {{"name", "class"}};
|
||||
for_3312 obj{};
|
||||
|
||||
j.get_to(obj);
|
||||
|
||||
CHECK(obj.name == "class");
|
||||
}
|
||||
|
||||
#if JSON_HAS_RANGES == 1
|
||||
SECTION("issue #4440 - assert when using std::views::filter and GCC 10")
|
||||
{
|
||||
auto noOpFilter = std::views::filter([](auto&&) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
json j = {1, 2, 3};
|
||||
auto filtered = j | noOpFilter;
|
||||
CHECK(*filtered.begin() == 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
|
||||
{
|
||||
std::vector<int> nums{1, 2, 37, 42, 21};
|
||||
auto filteredNums = nums | std::views::filter([](int i)
|
||||
{
|
||||
return i > 10;
|
||||
});
|
||||
json const j(filteredNums);
|
||||
CHECK(j.type() == json::value_t::array);
|
||||
CHECK(j == json({37, 42, 21}));
|
||||
}
|
||||
#endif
|
||||
|
||||
// owning_view is not available in libstdc++ < 12
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
|
||||
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
|
||||
{
|
||||
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
|
||||
{
|
||||
return i > 10;
|
||||
}));
|
||||
CHECK(j.type() == json::value_t::array);
|
||||
CHECK(j == json({37, 42, 21}));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
|
||||
{
|
||||
std::vector<int> nums{1, 2, 3};
|
||||
auto t = nums | std::views::transform([](int i) noexcept
|
||||
{
|
||||
return i * 2;
|
||||
});
|
||||
json const j(t);
|
||||
CHECK(j.type() == json::value_t::array);
|
||||
CHECK(j == json({2, 4, 6}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,12 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-regression3-cpp<NN>.cpp. This file stays C++11-only.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -48,38 +47,8 @@ using ordered_json = nlohmann::ordered_json;
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#include <any>
|
||||
#include <variant>
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#if __has_include(<optional>)
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4804
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#if __has_include(<span>)
|
||||
#include <span>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// the explicit instantiation for #4825 is in unit-explicit_instantiation.cpp
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4440
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if JSON_HAS_RANGES == 1
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
@@ -194,22 +163,6 @@ inline void from_json(const json& j, for_3171_base& tb) // NOLINT(misc-use-inter
|
||||
tb._from_json(j);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #3312
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
struct for_3312
|
||||
{
|
||||
std::string name;
|
||||
};
|
||||
|
||||
inline void from_json(const json& j, for_3312& obj) // NOLINT(misc-use-internal-linkage)
|
||||
{
|
||||
j.at("name").get_to(obj.name);
|
||||
}
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #3204
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -273,37 +226,8 @@ struct Example_3810
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example_3810, bla) // NOLINT(misc-use-internal-linkage)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4740
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
struct Example_4740
|
||||
{
|
||||
std::optional<std::string> host = std::nullopt;
|
||||
std::optional<int> port = std::nullopt;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_4740, host, port)
|
||||
};
|
||||
#endif
|
||||
|
||||
TEST_CASE("regression tests 3")
|
||||
{
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
// JSON_HAS_CPP_17 (do not remove; see note at top of file)
|
||||
SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ")
|
||||
{
|
||||
nlohmann::detail::std_fs::path text_path("/tmp/text.txt");
|
||||
const json j(text_path);
|
||||
|
||||
const auto j_path = j.get<nlohmann::detail::std_fs::path>();
|
||||
CHECK(j_path == text_path);
|
||||
|
||||
#if DOCTEST_CLANG || DOCTEST_GCC >= DOCTEST_COMPILER(8, 4, 0)
|
||||
// only known to work on Clang and GCC >=8.4
|
||||
CHECK_THROWS_WITH_AS(nlohmann::detail::std_fs::path(json(1)), "[json.exception.type_error.302] type must be string, but is number", json::type_error);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #3077 - explicit constructor with default does not compile")
|
||||
{
|
||||
@@ -354,31 +278,6 @@ TEST_CASE("regression tests 3")
|
||||
CHECK(td.str == "value");
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20")
|
||||
{
|
||||
// see test for #3171
|
||||
const ordered_json j = {{"name", "class"}};
|
||||
for_3312 obj{};
|
||||
|
||||
j.get_to(obj);
|
||||
|
||||
CHECK(obj.name == "class");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(JSON_HAS_CPP_17) && JSON_USE_IMPLICIT_CONVERSIONS
|
||||
SECTION("issue #3428 - Error occurred when converting nlohmann::json to std::any")
|
||||
{
|
||||
const json j;
|
||||
const std::any a1 = j;
|
||||
std::any&& a2 = j;
|
||||
|
||||
CHECK(a1.type() == typeid(j));
|
||||
CHECK(a2.type() == typeid(j));
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #3204 - ambiguous regression")
|
||||
{
|
||||
const for_3204_bar bar_from_foo([](for_3204_foo) noexcept {}); // NOLINT(performance-unnecessary-value-param)
|
||||
@@ -421,28 +320,6 @@ TEST_CASE("regression tests 3")
|
||||
CHECK(oj["test"].dump() == expected);
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("issue #4740 - build issue with std::optional")
|
||||
{
|
||||
const auto t1 = Example_4740();
|
||||
const auto j1 = nlohmann::json(t1);
|
||||
CHECK(j1.dump() == "{\"host\":null,\"port\":null}");
|
||||
const auto t2 = j1.get<Example_4740>();
|
||||
CHECK(!t2.host.has_value());
|
||||
CHECK(!t2.port.has_value());
|
||||
|
||||
// improve coverage
|
||||
auto t3 = Example_4740();
|
||||
t3.port = 80;
|
||||
t3.host = "example.com";
|
||||
const auto j2 = nlohmann::json(t3);
|
||||
CHECK(j2.dump() == "{\"host\":\"example.com\",\"port\":80}");
|
||||
const auto t4 = j2.get<Example_4740>();
|
||||
CHECK(t4.host.has_value());
|
||||
CHECK(t4.port.has_value());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(_MSVC_LANG)
|
||||
// MSVC returns garbage on invalid enum values, so this test is excluded
|
||||
// there.
|
||||
@@ -457,119 +334,7 @@ TEST_CASE("regression tests 3")
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||||
{
|
||||
const std::vector<std::uint8_t> data = {0x80};
|
||||
const auto decoded = json_4804::from_cbor(data);
|
||||
CHECK((decoded == json_4804::array()));
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||
{
|
||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||
const json_4804 j = original;
|
||||
CHECK(j.is_binary());
|
||||
CHECK(!j.is_array());
|
||||
|
||||
// Test round-tripping: extracting the binary value back as the custom container type
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted == original);
|
||||
|
||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||
CHECK(default_json.is_array());
|
||||
CHECK(!default_json.is_binary());
|
||||
}
|
||||
|
||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||
{
|
||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||
// (not just from a binary-typed node)
|
||||
const auto j = json_4804::parse("[1,2,3]");
|
||||
CHECK(j.is_array());
|
||||
CHECK(!j.is_binary());
|
||||
|
||||
// Extracting as custom BinaryType should work from arrays
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted.size() == 3);
|
||||
CHECK(extracted[0] == std::byte{1});
|
||||
CHECK(extracted[1] == std::byte{2});
|
||||
CHECK(extracted[2] == std::byte{3});
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
||||
{
|
||||
const json jval{};
|
||||
auto GetValue = [](const json & valRoot) -> std::optional<json>
|
||||
{
|
||||
if (valRoot.contains("default"))
|
||||
{
|
||||
return valRoot.at("default");
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
auto result = GetValue(jval);
|
||||
CHECK(!result.has_value());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_RANGES == 1
|
||||
SECTION("issue #4440 - assert when using std::views::filter and GCC 10")
|
||||
{
|
||||
auto noOpFilter = std::views::filter([](auto&&) noexcept
|
||||
{
|
||||
return true;
|
||||
});
|
||||
json j = {1, 2, 3};
|
||||
auto filtered = j | noOpFilter;
|
||||
CHECK(*filtered.begin() == 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
|
||||
{
|
||||
std::vector<int> nums{1, 2, 37, 42, 21};
|
||||
auto filteredNums = nums | std::views::filter([](int i)
|
||||
{
|
||||
return i > 10;
|
||||
});
|
||||
json const j(filteredNums);
|
||||
CHECK(j.type() == json::value_t::array);
|
||||
CHECK(j == json({37, 42, 21}));
|
||||
}
|
||||
#endif
|
||||
|
||||
// owning_view is not available in libstdc++ < 12
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
|
||||
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
|
||||
{
|
||||
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
|
||||
{
|
||||
return i > 10;
|
||||
}));
|
||||
CHECK(j.type() == json::value_t::array);
|
||||
CHECK(j == json({37, 42, 21}));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
|
||||
{
|
||||
std::vector<int> nums{1, 2, 3};
|
||||
auto t = nums | std::views::transform([](int i) noexcept
|
||||
{
|
||||
return i * 2;
|
||||
});
|
||||
json const j(t);
|
||||
CHECK(j.type() == json::value_t::array);
|
||||
CHECK(j == json({2, 4, 6}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||
@@ -914,7 +679,6 @@ TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with
|
||||
CHECK(mixed == json({{"keep", {{"a", 1}, {"b", 2}}}, {"replace", {{"x", 2}}}}));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("regression test #5476 - array type without reserve()")
|
||||
{
|
||||
// the capacity reserved for definite-length arrays must not require the
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. This whole file tests
|
||||
// std::format (C++20), so it cannot be split: it is built for C++20 as well:
|
||||
// JSON_HAS_CPP_20 (do not remove)
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -19,7 +17,6 @@
|
||||
using json = nlohmann::json;
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
#if JSON_HAS_STD_FORMAT
|
||||
|
||||
#include <iterator>
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// This file contains the C++20-only part of unit-user_defined_input.cpp (parsing with
|
||||
// std::counted_iterator and std::default_sentinel_t). It is kept in a separate translation
|
||||
// unit so the (much larger) unit-user_defined_input.cpp is built for C++11 only and not
|
||||
// rebuilt for every C++ standard.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__cpp_lib_concepts)
|
||||
#include <iterator>
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_concepts)
|
||||
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
|
||||
{
|
||||
using iterator_type = std::string::const_iterator;
|
||||
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
|
||||
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
|
||||
|
||||
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
|
||||
const json j = json::parse(first, std::default_sentinel);
|
||||
CHECK(j["key"] == "value");
|
||||
CHECK(j["array"].size() == 3);
|
||||
|
||||
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
|
||||
CHECK(json::accept(first2, std::default_sentinel));
|
||||
}
|
||||
|
||||
TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
|
||||
{
|
||||
// A sized sentinel makes the remaining element count computable in O(1), so
|
||||
// std::counted_iterator over a contiguous iterator must reach the same bulk
|
||||
// string/number scanners as a plain pointer - not just the byte-at-a-time
|
||||
// fallback (see #5268 for the equivalent memcpy fast path).
|
||||
#if JSON_HAS_RANGES
|
||||
// JSON_HAS_RANGES is 0 on standard libraries with an incomplete <ranges>
|
||||
// (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back
|
||||
// to the byte-at-a-time scanner; everything below still has to work there.
|
||||
using adapter_type = nlohmann::detail::iterator_input_adapter<std::counted_iterator<const char*>, std::default_sentinel_t>;
|
||||
CHECK(adapter_type::supports_bulk_scan);
|
||||
CHECK(adapter_type::supports_seek);
|
||||
#endif
|
||||
|
||||
// exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and
|
||||
// integer/floating-point numbers
|
||||
const std::string json_str =
|
||||
R"({"ascii":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",)"
|
||||
"\"utf8\":\"\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x80\xc3\xa9\","
|
||||
R"("escaped":"aéb\n\\","ints":[0,-1,18446744073709551615,-9223372036854775808],)"
|
||||
R"("floats":[1.5,-2.25e3,0.30000000000000004]})";
|
||||
const auto len = static_cast<std::iter_difference_t<const char*>>(json_str.size());
|
||||
|
||||
const std::counted_iterator<const char*> first(json_str.data(), len);
|
||||
const json j = json::parse(first, std::default_sentinel);
|
||||
|
||||
// parsing through the pointer adapter must give exactly the same result
|
||||
CHECK(j == json::parse(json_str));
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// Diagnostics that quote the offending token are reconstructed from the
|
||||
// already-consumed input (supports_seek), a path a sized sentinel only
|
||||
// reaches now; check a few that include the "last read" text. Parsing
|
||||
// invalid input aborts when exceptions are off, hence the guard.
|
||||
// Raw strings and explicit bytes: an escaped literal and two literals
|
||||
// written next to each other both read as mistakes to static analysis.
|
||||
const auto byte = [](int value)
|
||||
{
|
||||
return std::string(1, static_cast<char>(value));
|
||||
};
|
||||
const std::vector<std::string> diagnostic_docs =
|
||||
{
|
||||
"1\nx",
|
||||
"truX",
|
||||
"[tru]",
|
||||
R"("abc)",
|
||||
R"(["\ud834"])",
|
||||
R"(["a)" + byte(0x01) + R"(b"])",
|
||||
R"([")" + byte(0xC3) + byte(0x28) + R"("])",
|
||||
"[1e]",
|
||||
R"(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX)"
|
||||
};
|
||||
|
||||
for (const auto& text : diagnostic_docs)
|
||||
{
|
||||
CAPTURE(text)
|
||||
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
|
||||
std::string counted_message;
|
||||
std::string string_message;
|
||||
try
|
||||
{
|
||||
const json counted_result = json::parse(it, std::default_sentinel);
|
||||
static_cast<void>(counted_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
counted_message = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
const json string_result = json::parse(text);
|
||||
static_cast<void>(string_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
string_message = e.what();
|
||||
}
|
||||
CHECK_FALSE(counted_message.empty());
|
||||
CHECK(counted_message == string_message);
|
||||
}
|
||||
|
||||
// and errors must still be reported identically
|
||||
const std::string bad = "[01\n]";
|
||||
const std::counted_iterator<const char*> bad_first(bad.data(), static_cast<std::iter_difference_t<const char*>>(bad.size()));
|
||||
std::string counted_what;
|
||||
std::string string_what;
|
||||
try
|
||||
{
|
||||
const json counted_result = json::parse(bad_first, std::default_sentinel);
|
||||
static_cast<void>(counted_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
counted_what = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
const json string_result = json::parse(bad);
|
||||
static_cast<void>(string_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
string_what = e.what();
|
||||
}
|
||||
CHECK_FALSE(counted_what.empty());
|
||||
CHECK(counted_what == string_what);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// several cases below are truncated on purpose, and parsing invalid input
|
||||
// aborts when exceptions are off
|
||||
TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
|
||||
{
|
||||
// The count, not the size of the underlying buffer, is the end of the
|
||||
// input: the bulk scanners must never look at the bytes behind it, even
|
||||
// though they are readable. Each case is compared against parsing the
|
||||
// equivalent prefix as a std::string.
|
||||
const auto via_counted = [](const std::string & buf, std::size_t n) -> std::string
|
||||
{
|
||||
const std::counted_iterator<const char*> first(buf.data(), static_cast<std::iter_difference_t<const char*>>(n));
|
||||
try
|
||||
{
|
||||
const json j = json::parse(first, std::default_sentinel);
|
||||
return "OK|" + j.dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
return {e.what()};
|
||||
}
|
||||
};
|
||||
const auto via_prefix = [](const std::string & buf, std::size_t n) -> std::string
|
||||
{
|
||||
try
|
||||
{
|
||||
const json j = json::parse(buf.substr(0, n));
|
||||
return "OK|" + j.dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
return {e.what()};
|
||||
}
|
||||
};
|
||||
|
||||
struct testcase // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
||||
{
|
||||
const char* buffer;
|
||||
std::size_t count;
|
||||
};
|
||||
const std::vector<testcase> cases =
|
||||
{
|
||||
{"[\"abc\"]____TRAILING____", 7}, // exact fit, tail hidden
|
||||
{"[\"abcdefghijklmnop\"]____", 8}, // cut inside a string
|
||||
{"[\"abc\"]____", 6}, // cut just before the closing quote
|
||||
{"[12345]xxxxx", 4}, // cut inside a number
|
||||
{"[123]999999", 5}, // number ends exactly at the count
|
||||
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 12}, // closing quote only behind the count
|
||||
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 19}, // cut inside an 8-byte SWAR stride
|
||||
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]", 5}, // cut inside a UTF-8 sequence
|
||||
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]____", 10}, // complete UTF-8, tail hidden
|
||||
{"[1.25e3]TRAILINGDIGITS999", 7}, // number token reaches the count
|
||||
};
|
||||
|
||||
for (const auto& tc : cases)
|
||||
{
|
||||
CAPTURE(tc.buffer)
|
||||
CAPTURE(tc.count)
|
||||
const std::string buffer = tc.buffer;
|
||||
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -6,12 +6,11 @@
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||
// When using macros that are only defined for particular versions of the standard
|
||||
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||
// version macro in a comment close by, like this:
|
||||
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
||||
// go into a separate file unit-user_defined_input-cpp<NN>.cpp. This file stays C++11-only.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
@@ -25,10 +24,6 @@ using nlohmann::json;
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
|
||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
||||
#include <iterator>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
TEST_CASE("Use arbitrary stdlib container")
|
||||
@@ -277,196 +272,4 @@ TEST_CASE("Contiguous byte containers take the pointer adapter")
|
||||
CHECK(json::parse(buffer) == json({1, 2, 3, 4, 5}));
|
||||
}
|
||||
|
||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
||||
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
|
||||
{
|
||||
using iterator_type = std::string::const_iterator;
|
||||
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
|
||||
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
|
||||
|
||||
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
|
||||
const json j = json::parse(first, std::default_sentinel);
|
||||
CHECK(j["key"] == "value");
|
||||
CHECK(j["array"].size() == 3);
|
||||
|
||||
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
|
||||
CHECK(json::accept(first2, std::default_sentinel));
|
||||
}
|
||||
|
||||
TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
|
||||
{
|
||||
// A sized sentinel makes the remaining element count computable in O(1), so
|
||||
// std::counted_iterator over a contiguous iterator must reach the same bulk
|
||||
// string/number scanners as a plain pointer - not just the byte-at-a-time
|
||||
// fallback (see #5268 for the equivalent memcpy fast path).
|
||||
#if JSON_HAS_RANGES
|
||||
// JSON_HAS_RANGES is 0 on standard libraries with an incomplete <ranges>
|
||||
// (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back
|
||||
// to the byte-at-a-time scanner; everything below still has to work there.
|
||||
using adapter_type = nlohmann::detail::iterator_input_adapter<std::counted_iterator<const char*>, std::default_sentinel_t>;
|
||||
CHECK(adapter_type::supports_bulk_scan);
|
||||
CHECK(adapter_type::supports_seek);
|
||||
#endif
|
||||
|
||||
// exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and
|
||||
// integer/floating-point numbers
|
||||
const std::string json_str =
|
||||
R"({"ascii":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",)"
|
||||
"\"utf8\":\"\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x80\xc3\xa9\","
|
||||
R"("escaped":"aéb\n\\","ints":[0,-1,18446744073709551615,-9223372036854775808],)"
|
||||
R"("floats":[1.5,-2.25e3,0.30000000000000004]})";
|
||||
const auto len = static_cast<std::iter_difference_t<const char*>>(json_str.size());
|
||||
|
||||
const std::counted_iterator<const char*> first(json_str.data(), len);
|
||||
const json j = json::parse(first, std::default_sentinel);
|
||||
|
||||
// parsing through the pointer adapter must give exactly the same result
|
||||
CHECK(j == json::parse(json_str));
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// Diagnostics that quote the offending token are reconstructed from the
|
||||
// already-consumed input (supports_seek), a path a sized sentinel only
|
||||
// reaches now; check a few that include the "last read" text. Parsing
|
||||
// invalid input aborts when exceptions are off, hence the guard.
|
||||
// Raw strings and explicit bytes: an escaped literal and two literals
|
||||
// written next to each other both read as mistakes to static analysis.
|
||||
const auto byte = [](int value)
|
||||
{
|
||||
return std::string(1, static_cast<char>(value));
|
||||
};
|
||||
const std::vector<std::string> diagnostic_docs =
|
||||
{
|
||||
"1\nx",
|
||||
"truX",
|
||||
"[tru]",
|
||||
R"("abc)",
|
||||
R"(["\ud834"])",
|
||||
R"(["a)" + byte(0x01) + R"(b"])",
|
||||
R"([")" + byte(0xC3) + byte(0x28) + R"("])",
|
||||
"[1e]",
|
||||
R"(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX)"
|
||||
};
|
||||
|
||||
for (const auto& text : diagnostic_docs)
|
||||
{
|
||||
CAPTURE(text)
|
||||
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
|
||||
std::string counted_message;
|
||||
std::string string_message;
|
||||
try
|
||||
{
|
||||
const json counted_result = json::parse(it, std::default_sentinel);
|
||||
static_cast<void>(counted_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
counted_message = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
const json string_result = json::parse(text);
|
||||
static_cast<void>(string_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
string_message = e.what();
|
||||
}
|
||||
CHECK_FALSE(counted_message.empty());
|
||||
CHECK(counted_message == string_message);
|
||||
}
|
||||
|
||||
// and errors must still be reported identically
|
||||
const std::string bad = "[01\n]";
|
||||
const std::counted_iterator<const char*> bad_first(bad.data(), static_cast<std::iter_difference_t<const char*>>(bad.size()));
|
||||
std::string counted_what;
|
||||
std::string string_what;
|
||||
try
|
||||
{
|
||||
const json counted_result = json::parse(bad_first, std::default_sentinel);
|
||||
static_cast<void>(counted_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
counted_what = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
const json string_result = json::parse(bad);
|
||||
static_cast<void>(string_result);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
string_what = e.what();
|
||||
}
|
||||
CHECK_FALSE(counted_what.empty());
|
||||
CHECK(counted_what == string_what);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// several cases below are truncated on purpose, and parsing invalid input
|
||||
// aborts when exceptions are off
|
||||
TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
|
||||
{
|
||||
// The count, not the size of the underlying buffer, is the end of the
|
||||
// input: the bulk scanners must never look at the bytes behind it, even
|
||||
// though they are readable. Each case is compared against parsing the
|
||||
// equivalent prefix as a std::string.
|
||||
const auto via_counted = [](const std::string & buf, std::size_t n) -> std::string
|
||||
{
|
||||
const std::counted_iterator<const char*> first(buf.data(), static_cast<std::iter_difference_t<const char*>>(n));
|
||||
try
|
||||
{
|
||||
const json j = json::parse(first, std::default_sentinel);
|
||||
return "OK|" + j.dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
return {e.what()};
|
||||
}
|
||||
};
|
||||
const auto via_prefix = [](const std::string & buf, std::size_t n) -> std::string
|
||||
{
|
||||
try
|
||||
{
|
||||
const json j = json::parse(buf.substr(0, n));
|
||||
return "OK|" + j.dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
return {e.what()};
|
||||
}
|
||||
};
|
||||
|
||||
struct testcase // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
||||
{
|
||||
const char* buffer;
|
||||
std::size_t count;
|
||||
};
|
||||
const std::vector<testcase> cases =
|
||||
{
|
||||
{"[\"abc\"]____TRAILING____", 7}, // exact fit, tail hidden
|
||||
{"[\"abcdefghijklmnop\"]____", 8}, // cut inside a string
|
||||
{"[\"abc\"]____", 6}, // cut just before the closing quote
|
||||
{"[12345]xxxxx", 4}, // cut inside a number
|
||||
{"[123]999999", 5}, // number ends exactly at the count
|
||||
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 12}, // closing quote only behind the count
|
||||
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 19}, // cut inside an 8-byte SWAR stride
|
||||
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]", 5}, // cut inside a UTF-8 sequence
|
||||
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]____", 10}, // complete UTF-8, tail hidden
|
||||
{"[1.25e3]TRAILINGDIGITS999", 7}, // number token reaches the count
|
||||
};
|
||||
|
||||
for (const auto& tc : cases)
|
||||
{
|
||||
CAPTURE(tc.buffer)
|
||||
CAPTURE(tc.count)
|
||||
const std::string buffer = tc.buffer;
|
||||
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
Reference in new issue
Block a user