mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 13:10:33 +00:00
* Drop stale LCOV_EXCL_LINE from the json_pointer out_of_range.410 throw The comment said the size_type overflow check in array_index() is only triggered on special platforms like 32-bit, and the throw was excluded from coverage. On 64-bit platforms the check is true for SIZE_MAX itself, and unit-json_pointer.cpp has asserted that case four times since #5395, so the line is executed in the coverage job. Reword the comment and remove the exclusion marker so the coverage report notices if the tests stop reaching it. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Name all three C-array check aliases in the enum-macro NOLINTs NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT) suppressed the c-array warning under modernize-avoid-c-arrays only, but clang-tidy emits the same diagnostic under the aliases cppcoreguidelines-avoid-c-arrays and hicpp-avoid-c-arrays too. Any user running those checks got a false positive at every macro expansion, and our own tests needed a local NOLINT at each call site to work around it. Name all three aliases in the four macro comments instead, and drop the now-redundant c-array names from the five test call-site NOLINTs. Comment-only change; behavior, the public API, and the ABI do not change. Ran make amalgamate. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Include doctest as a SYSTEM directory instead of disabling warnings for all tests test_main added -Wno-deprecated and -Wno-float-equal as PUBLIC compile options for every non-MSVC compiler, so they were applied to every translation unit, library headers included, and silenced the CI warnings meant to check the library's own -Wfloat-equal pragmas. The only code that actually needed the suppression was the vendored doctest.h, which was included as a normal (non-SYSTEM) directory. Include thirdparty/doctest as SYSTEM for test_main, matching what tests/abi/CMakeLists.txt already does, and drop the two suppressions from both targets. Verified locally that unit-comparison, unit-conversions and unit-constructor1 compile clean with -Werror -Weverything and doctest as -isystem, and that CMake still configures with JSON_BuildTests=ON. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the no-op ci_clang_analyze target ci_clang_analyze configured the build with the real compiler and only then wrapped ninja with scan-build. scan-build intercepts compiles by overriding CC/CXX, but build.ninja already had the compiler path baked in from the configure step, so every run bypassed the analyzer: CI logs show "No bugs found" after a normal build, never an analysis. The job also used Debian's frozen clang-tools-14 rather than the image's own clang, and CLANG_ANALYZER_CHECKS still named three valist.* checkers that current clang merged into security.VAList. ci_clang_tidy already runs every clang-analyzer-* check (via .clang-tidy's "Checks: '*'") with warnings as errors, so nothing is lost by removing the dead job. Delete ci_clang_analyze, CLANG_ANALYZER_CHECKS and the SCAN_BUILD_TOOL lookup from cmake/ci.cmake, drop it from the ubuntu.yml ci_static_analysis_clang matrix, and drop the now-unused clang-tools apt package (iwyu stays for ci_single_binaries). Reword quality_assurance.md and assurance_case.md, which described the dead job as a working control, to say the Clang Static Analyzer checks run through clang-tidy. Verified that `cmake -DJSON_CI=ON` still configures cleanly and that ci_clang_analyze no longer appears in the generated build or in any CMake/workflow file. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Re-enable portability-template-virtual-member-function; remove redundant forwards .clang-tidy disabled three checks "to get the CI going" (#4489, 2024-11-13): portability-template-virtual-member-function, bugprone-use-after-move and its alias hicpp-invalid-access-moved. portability-template-virtual-member-function only flagged output_stream_adapter::write_character/write_characters; annotate both with NOLINT and re-enable the check. bugprone-use-after-move flagged several double forwards that have no effect at runtime: - from_json.hpp calls std::forward<BasicJsonType>(j).at(Idx) inside pack expansions; at() has no ref-qualified overloads and always returns an lvalue reference, so the forward is a no-op. Replace with plain j.at(Idx) in all four places. - the move constructor forwards the whole object to its base class and then reads other's members. That is item 9 of #5724 (together with its cppcheck suppressions) and is left to that change. - input_adapters.hpp forwards the container twice on purpose, so the begin/end iterator types match adapter_type; annotate with NOLINT and a comment instead of changing behavior. The check still flags the move constructor (see above) and two sites in at(KeyType&&) (both overloads, json.hpp, in the throw's string_t(std::forward<KeyType>(key)) after find(std::forward<KeyType>(key))). Open PR #5689 rewrites that hunk, so bugprone-use-after-move (and hicpp-invalid-access-moved) stay disabled for now, with a comment explaining why; re-enable them once #5689 and the #5724 move-constructor change have landed. Also resolve the portability-avoid-pragma-once TODO: single_include never has #pragma once (amalgamate.py strips it) and every supported compiler accepts it in include/, so keep it disabled with an explanatory comment instead of a TODO. Fix the stale "json.hpp, around line 1265" comment in unit-class_parser.cpp, which now points at the move constructor's actual line. Behavior, the public API and the ABI do not change. Verified with clang-tidy 22.1.8 that portability-template-virtual-member-function now reports nothing, that bugprone-use-after-move/ hicpp-invalid-access-moved report only the known at(KeyType&&) and move-constructor sites, and that unit-custom-base-class, unit-constructor1, unit-conversions, unit-element_access2, unit-class_parser and unit-diagnostic-positions (JSON_DIAGNOSTIC_POSITIONS=1) compile under ASan/UBSan and pass with the same assertion counts as before. Ran make amalgamate. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix stale and malformed NOLINT comments json_sax.hpp named "-warnings-as-errors" in the NOLINT list on the two JSON_ASSERT(false) lines; that is the suffix clang-tidy appends to a diagnostic tag under WarningsAsErrors, not a check name, and every other JSON_ASSERT(false) omits it. unit-capacity.cpp carried 30 "// NOLINT(misc-const-correctness)" comments on "json j = ...;" declarations that are all used with non-const members afterwards, so the check has nothing to report there. unit-constructor2.cpp used a blanket "// NOLINT: access after move is OK here" on a use-after-move that hides every check on the line; naming bugprone-use-after-move and hicpp-invalid-access-moved keeps the intent once those checks are re-enabled (#5724). Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 10 * Remove stale .clang-tidy entries -google-runtime-references disabled a check that neither clang-tidy 22.1.8 nor 23.1.2 lists under --list-checks -checks='*'; it was removed upstream. The commented-out HeaderFilterRegex line has been unused since the active HeaderFilterRegex was introduced in #2561 (2021). Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 11 * Remove the GCC C++20 -Wignored-attributes pragma in json.hpp The pragma (added in #5164) claimed to work around the C++ modules redefinition errors of #5103, but #5103 is about hard errors (e.g. "redefinition of std::__is_constant_evaluated()", conflicting std::integral_constant) that ignoring a warning cannot suppress; they are traced to GCC PR 124430 and reproduce with <map> or <string> instead of json.hpp too. A GCC 16.2 -std=gnu++20 -fmodules build following #5103's repro steps still fails with the pragma in place, and a build of all test TUs with GCC_CXXFLAGS (which enable -Wignored-attributes) and the pragma removed produces no such warning. The block only hid a warning class from GCC C++20 users while suggesting #5103 was handled. Overlaps #5610, whose hunks touch the closing half of this pragma to insert the json_literals.hpp include. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 9 * Fix stale doxygen comments hidden by the -Wdocumentation pragma macro_scope.hpp ignores -Wdocumentation and -Wdocumentation-unknown-command for the whole library, which also hides genuine documentation mistakes: - detail::unescape() documented "@return unescaped string" but returns void and unescapes its argument in place; reworded to "@param[in,out] s string to unescape in place" and dropped the bogus @return. - basic_json::get()'s copy-conversion overload wrote "converted to @tparam ValueType" inside @return, which Doxygen and Clang parse as a second, malformed @tparam; changed to "@a ValueType", matching the two other get() overloads a few lines above that already use it. This narrows the gap the -Wdocumentation pragma needs to cover; fully replacing the Doxygen-only commands it also hides (item 2c) is left for after #5267. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 2 * Fix -Wextra-semi-stmt at its actual source, not assert() clang_flags.cmake blamed the global -Wno-extra-semi-stmt on assert(), but assert() expands to an expression under glibc and libc++ and does not trigger this warning. unit-assert_macro.cpp overrides JSON_ASSERT with "{if (!(x)) ++assert_counter; }", a bare block followed by a semicolon at every JSON_ASSERT(...) call site in the library; that was the actual source of 151 of the 208 -Wextra-semi-stmt sites found in a Clang 22 -Weverything sweep of the test suite with the flag removed. Switched to the standard do/while(false) macro idiom, which does not expand to a statement-plus-semicolon, and corrected the comment to name the remaining source instead: vendored Doctest's CAPTURE(x) shim, which already ends in a semicolon. Verified with clang++ -Wextra-semi-stmt (plus the file's other CI ignores) that unit-assert_macro.cpp now compiles without any -Wextra-semi-stmt diagnostic. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 8 (step 1 of 2; step 2 covers the CAPTURE() call sites) * Drop the redundant semicolon from CAPTURE() call sites; remove -Wno-extra-semi-stmt doctest_compatibility.h defines CAPTURE(x) as DOCTEST_CAPTURE(x); (with a trailing semicolon baked into the macro), specifically so call sites do not need to add one themselves; most of the ~267 call sites already follow that convention. The remaining 64 call sites across 20 files wrote "CAPTURE(x);" anyway, turning into a statement plus an empty statement and triggering -Wextra-semi-stmt. Dropped the redundant semicolon at each of those sites. With item 6 having already made vendored Doctest a SYSTEM include, and this the last known source of -Wextra-semi-stmt findings, removed the flag from clang_flags.cmake entirely. Verified with clang++ -Wextra-semi-stmt (plus the file's other CI ignores) that all 20 touched files, plus a file with no CAPTURE() use (unit-json_pointer.cpp), compile without any -Wextra-semi-stmt diagnostic. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 8 (step 2 of 2) * Switch ci_static_analysis_clang off the frozen LLVM 22 dev image ubuntu.yml pinned the clang-tidy/clang-tidy-sanitizer/single-binaries job to silkeh/clang:dev, a tag last pushed 2026-02-18 that reports "clang version 22.0.0 (...+20251015...)", a pre-release snapshot from before the LLVM 22 release; the maintainer now updates dev-unstable, 22, and latest instead. Switched to silkeh/clang:22, matching the other clang jobs on :latest. Verified with clang-tidy 22.1.8 (the image's actual version) against this repository's .clang-tidy and library headers what the release image newly reports compared to :dev: - readability-redundant-typename fires at ~250 sites across the _cpp20-relevant conversion/to_chars headers; the library targets C++11 and keeps the typenames, so the check is disabled in .clang-tidy, matching how the file already handles checks that don't fit a C++11 codebase. - misc-anonymous-namespace-in-header fires on the two anonymous namespaces in from_json.hpp and to_json.hpp; added the alias to their existing NOLINT (cert-dcl59-cpp, fuchsia-header-anon-namespaces, google-build-namespaces). - bugprone-std-namespace-modification fires on every addition to namespace std: the std::hash, std::formatter and std::swap overloads in json.hpp, and the std::tuple_size/std::tuple_element specializations in iteration_proxy.hpp (this last file is not named in #5725's item 5, found by actually running clang-tidy 22.1.8 against the current tree). All six are legal, deliberate additions to namespace std (explicit/partial specializations of std types, or the pre-C++20 std::swap overload); annotated each with the check name next to its existing cert-dcl58-cpp NOLINT. - modernize-avoid-c-style-cast reported nothing new. Also added clang++-22/21, clang-tidy-22/21, g++-16 and gcov-16 to the find_program search lists in ci.cmake so a local "maximal warnings" configure prefers the current toolchain version over an older one on PATH. #5725 item 5 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate cmake/gcc_flags.cmake for GCC 16.2.0 GCC_CXXFLAGS was generated for GCC 15.1.0, but ci_test_gcc and ci_test_gcc_cxx{11..26} now run in gcc:latest, currently GCC 16.2.0, so the "maximal warnings" job was missing warnings introduced since 15.1.0 while carrying entries GCC 16 treats as duplicates or no-ops. Regenerated with https://github.com/nlohmann/gcc_flags (patched locally to not crash on an option whose "-x c++ <opt> -" probe fails before it reads stdin, e.g. -Wabi=; the tool otherwise raises BrokenPipeError instead of recording the option as an error) run against g++ 16.2.0 in the official gcc:16 Docker image, keeping the documented -Wno-* exclusions and the same alphabetical placement scheme as before. Also added three GCC 16 warnings the generator cannot discover on its own because it only probes value ranges/lists it finds in the -Q option name itself, not in the enum choices --help=warnings documents separately: - -Wbidi-chars=any, -Wleading-whitespace=spaces: manually verified these compile cleanly with g++ 16.2.0. - -Wstrict-flex-arrays: deliberately NOT added, unlike the other two. Without -fstrict-flex-arrays (which the library does not enable, as it would change codegen for flexible array members), GCC prints "'-Wstrict-flex-arrays' is ignored when '-fstrict-flex-arrays' is not present" on every translation unit, and under our -Werror that note itself aborts the build. This differs from the harmless no-op warnings already kept in the file (-Whsa, -Wsynth, -Wunreachable-code, -Wunsafe-loop-optimizations), which emit nothing; #5725 item 7 named -Wstrict-flex-arrays as one of the flags GCC 16 adds, but did not anticipate this failure mode. Verified: compiled the library header and a representative set of test translation units (including ones touched by items 1, 3, 8, 9, 10 of this issue) with the regenerated GCC_CXXFLAGS plus -Werror under g++ 16.2.0 at -std=c++11 through -std=c++26, with zero warnings; ran the full local test suite (129/129 passing, unrelated to this compiler) as a regression check. CI must still confirm the actual ci_test_gcc / ci_test_standards_gcc targets end to end, since this was verified with direct g++ invocations rather than through the CMake/ CXXFLAGS environment-variable plumbing in ci.cmake. #5725 item 7 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Avoid std::basic_string<CharType> for non-character output_adapter CharType output_adapter<CharType, StringType> defaulted StringType to std::basic_string<CharType>, and (with JSON_NO_IO undefined) always declared a std::basic_ostream<CharType>&-taking constructor. For CharType with no non-deprecated std::char_traits specialization (only std::uint8_t is ever used this way, by the binary writers), simply naming either type - as an unused default template argument, or as an unused, never-called constructor's parameter type - instantiates std::char_traits<CharType> merely to name it, which some standard libraries mark deprecated: with the library-wide -Wdocumentation pragma (item 2's other half, left for a later commit) temporarily removed, an Apple clang 21 / libc++ TU calling json::to_cbor(j, vec) with std::vector<std::uint8_t>& got one -Wdeprecated-declarations warning per binary writer at the old output_adapters.hpp:193. Replaced the eager std::basic_string<CharType> / std::basic_ostream <CharType> defaults with a bool-tagged partial specialization (not std::conditional, which requires naming both branches' types up front regardless of which is selected, reproducing the same warning) that only ever names std::basic_string<CharType> / std::basic_ostream <CharType> when CharType is actually one of char, wchar_t, char16_t, char32_t, or (with __cpp_lib_char8_t) char8_t. For any other CharType, output_adapter's StringType and ostream-constructor parameter fall back to two distinct empty placeholder types, kept distinct so the two constructor overloads do not collide into a single redeclaration. Public API / behavior: passing a std::basic_string<std::uint8_t>& or std::basic_ostream<std::uint8_t>& directly to a binary writer's output_adapter now fails to compile instead of compiling with a deprecation warning; this was neither documented nor tested. All documented uses (std::vector<CharType>, std::basic_ostream<CharType> and StringType for character CharType) are unaffected. Verified with Apple clang 21 / libc++, with the two -Wdocumentation* "ignored" pragma lines in macro_scope.hpp temporarily removed and -std=c++11/c++20 plus the project's -Weverything flag set: calling to_cbor/to_msgpack/to_ubjson/to_bjdata/to_bson/to_bon8 on a std::vector<std::uint8_t> now produces no char_traits<unsigned char> (or any other) deprecation warning, while the char-based string- and ostream-adapter paths, and a to_cbor/from_cbor round trip, still compile and run correctly; also verified with GCC 16.2.0. Ran the full local test suite, including the binary-format unit tests (unit-cbor, unit-msgpack, unit-ubjson, unit-bjdata, unit-bson, unit-bon8, unit-binary_writer_sinks, unit-binary_formats, unit-custom-binary-type): 129/129 passing. #5725 item 2 (step a) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the library-wide -Wdocumentation pragma; fix what it hid macro_scope.hpp / macro_unscope.hpp pushed and popped a Clang diagnostic region over the entire library that ignored -Wdocumentation and -Wdocumentation-unknown-command. Removed both pragmas and fixed every finding a full -Wdocumentation (which implies -Wdocumentation-unknown-command and -Wdocumentation-deprecated-sync) build reports, so the library now compiles clean under Clang's documentation checks without a blanket suppression. Overlaps #5267, which is still open and edits a nearby doc block (json.hpp's get()/get_impl() @return, already fixed in the item 2 step (b) commit of this branch); this commit does not touch that block again. Unknown Doxygen alias commands (Doxyfile removed in #3071, so these were never rendered by anything) rewritten as plain prose, keeping the same information: - @requirement REQ-JSON-01 / REQ-JSON-02 (iter_impl.hpp, json_reverse_iterator.hpp): now "This class satisfies the following concept requirements (REQ-JSON-0N):". - @liveexample{prose,example-id} (three sites in json.hpp): kept the prose, dropped the command wrapper and the trailing example-id (docs/mkdocs/docs/examples/*.cpp still exist and are used directly by the rendered docs, not through this in-header alias) and unescaped the "\," commas that were only needed for the old alias's comma-separated argument syntax. - @complexity X (json.hpp x4, json_pointer.hpp x2, serializer.hpp x1): now "Complexity: X". Backslash sequences Clang's comment lexer tried to parse as commands, escaped to render as literal backslashes: - lexer.hpp get_codepoint(): two `\u` occurrences. - binary_reader.hpp get_bson_cstr() / get_bson_cstr_bulk(): two `\x00` occurrences. - serializer.hpp: three `\uXXXX` occurrences (constructor @param, append_codepoint_to_string_buffer() @brief, and the ensure_ascii member comment). One finding remained after all of the above: Clang reports "declaration is marked with '@deprecated' command but does not have a deprecation attribute" on the deprecated sax_parse(span_input_adapter&&, ...) overload, even though JSON_HEDLEY_DEPRECATED_FOR does expand to __attribute__((deprecated(...))) for Clang. Several isolated reproductions of this exact declaration shape - doc comment, template<>, two stacked __attribute__ macros, an overload set sharing the name - did not reproduce the warning, so this looks like a Clang comment/declaration-association quirk specific to this overload inside the much larger basic_json class template, not an actual documentation defect. Rather than keep the pragma library-wide for one Clang false positive, added a tightly scoped -Wdocumentation-deprecated-sync push/pop around just that overload. Verified with Apple clang 21 and the project's actual -Weverything flag set (cmake/clang_flags.cmake) on the full header at -std=c++11 and -std=c++20: zero -Wdocumentation* diagnostics. Also compiled clean with GCC 16.2.0 (the pragmas are already __clang__-gated, so this only confirms no unrelated breakage). Ran make check-amalgamation and the full local test suite: 129/129 passing. #5725 item 2 (step c) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Take the JSON value by const reference in the array and tuple from_json paths Review feedback on #5737 (gregmarr): once the no-op std::forward calls are gone, the forwarding references have no purpose. from_json_fn passes the value as const BasicJsonType&, so these functions were only ever instantiated with a const lvalue anyway. The std::array, std::pair and std::tuple overloads of from_json and their helpers now take const BasicJsonType& and pass j on unchanged. Because the deduced BasicJsonType is now the plain type, tuple_type and the static_assert name const BasicJsonType& explicitly, so the reference checks are unchanged: get<std::tuple<const std::string&>>() still works, and get<std::tuple<std::string&>>() still fails the same static_assert. from_json_tuple_get_impl keeps its forwarding reference, since tuple_type calls it through std::declval. Behavior, the public API and the ABI do not change. unit-conversions, unit-constructor1, unit-udt, unit-udt_macro, unit-regression1/2/3, unit-deserialization, unit-noexcept, unit-items, unit-allocator, unit-custom-object-type, unit-ordered_json2 and unit-brace-init-copy-semantics pass at C++11, C++17 and C++20 with unchanged assertion counts. Ran make amalgamate. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
924 lines
31 KiB
C++
924 lines
31 KiB
C++
// __ _____ _____ _____
|
||
// __| | __| | | | 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
|
||
|
||
// 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)
|
||
|
||
#include "doctest_compatibility.h"
|
||
|
||
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||
#include <locale>
|
||
|
||
// 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
|
||
|
||
#define JSON_TESTS_PRIVATE
|
||
#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
|
||
|
||
#include <cstdio>
|
||
#include <deque>
|
||
#include <list>
|
||
#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::basic_json<std::map, // ObjectType
|
||
std::vector, // ArrayType
|
||
std::string, // StringType
|
||
bool, // BooleanType
|
||
std::int64_t, // NumberIntegerType
|
||
std::uint64_t, // NumberUnsignedType
|
||
double, // NumberFloatType
|
||
std::allocator, // AllocatorType
|
||
nlohmann::adl_serializer, // JSONSerializer
|
||
std::vector<std::byte>, // BinaryType
|
||
void // CustomBaseClass
|
||
>;
|
||
#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")
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3077
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
class FooAlloc
|
||
{};
|
||
|
||
class Foo
|
||
{
|
||
public:
|
||
explicit Foo(const FooAlloc& /* unused */ = FooAlloc()) {}
|
||
|
||
bool value = false;
|
||
};
|
||
|
||
class FooBar
|
||
{
|
||
public:
|
||
Foo foo{}; // NOLINT(readability-redundant-member-init)
|
||
};
|
||
|
||
inline void from_json(const nlohmann::json& j, FooBar& fb) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
j.at("value").get_to(fb.foo.value);
|
||
}
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3171
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct for_3171_base // NOLINT(cppcoreguidelines-special-member-functions)
|
||
{
|
||
for_3171_base(const std::string& /*unused*/ = {}) {}
|
||
virtual ~for_3171_base();
|
||
|
||
for_3171_base(const for_3171_base& other) // NOLINT(hicpp-use-equals-default,modernize-use-equals-default)
|
||
: str(other.str)
|
||
{}
|
||
|
||
for_3171_base& operator=(const for_3171_base& other)
|
||
{
|
||
if (this != &other)
|
||
{
|
||
str = other.str;
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
for_3171_base(for_3171_base&& other) noexcept
|
||
: str(std::move(other.str))
|
||
{}
|
||
|
||
for_3171_base& operator=(for_3171_base&& other) noexcept
|
||
{
|
||
if (this != &other)
|
||
{
|
||
str = std::move(other.str);
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
virtual void _from_json(const json& j)
|
||
{
|
||
j.at("str").get_to(str);
|
||
}
|
||
|
||
std::string str{}; // NOLINT(readability-redundant-member-init)
|
||
};
|
||
|
||
for_3171_base::~for_3171_base() = default;
|
||
|
||
struct for_3171_derived : public for_3171_base
|
||
{
|
||
for_3171_derived() = default;
|
||
~for_3171_derived() override;
|
||
explicit for_3171_derived(const std::string& /*unused*/) { }
|
||
|
||
for_3171_derived(const for_3171_derived& other) // NOLINT(hicpp-use-equals-default,modernize-use-equals-default)
|
||
: for_3171_base(other)
|
||
{}
|
||
|
||
for_3171_derived& operator=(const for_3171_derived& other)
|
||
{
|
||
if (this != &other)
|
||
{
|
||
for_3171_base::operator=(other); // Call base class assignment operator
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
for_3171_derived(for_3171_derived&& other) noexcept
|
||
: for_3171_base(std::move(other))
|
||
{}
|
||
|
||
for_3171_derived& operator=(for_3171_derived&& other) noexcept
|
||
{
|
||
if (this != &other)
|
||
{
|
||
for_3171_base::operator=(std::move(other)); // Call base class move assignment operator
|
||
}
|
||
return *this;
|
||
}
|
||
};
|
||
|
||
for_3171_derived::~for_3171_derived() = default;
|
||
|
||
inline void from_json(const json& j, for_3171_base& tb) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
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
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct for_3204_foo
|
||
{
|
||
for_3204_foo() = default;
|
||
explicit for_3204_foo(std::string /*unused*/) {} // NOLINT(performance-unnecessary-value-param)
|
||
};
|
||
|
||
struct for_3204_bar
|
||
{
|
||
enum constructed_from_t // NOLINT(cppcoreguidelines-use-enum-class)
|
||
{
|
||
constructed_from_none = 0,
|
||
constructed_from_foo = 1,
|
||
constructed_from_json = 2
|
||
};
|
||
|
||
explicit for_3204_bar(std::function<void(for_3204_foo)> /*unused*/) noexcept // NOLINT(performance-unnecessary-value-param)
|
||
: constructed_from(constructed_from_foo) {}
|
||
explicit for_3204_bar(std::function<void(json)> /*unused*/) noexcept // NOLINT(performance-unnecessary-value-param)
|
||
: constructed_from(constructed_from_json) {}
|
||
|
||
constructed_from_t constructed_from = constructed_from_none;
|
||
};
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3333
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct for_3333 final
|
||
{
|
||
for_3333(int x_ = 0, int y_ = 0) : x(x_), y(y_) {}
|
||
|
||
template <class T>
|
||
for_3333(const T& /*unused*/)
|
||
{
|
||
CHECK(false);
|
||
}
|
||
|
||
int x = 0;
|
||
int y = 0;
|
||
};
|
||
|
||
template <>
|
||
inline for_3333::for_3333(const json& j)
|
||
: for_3333(j.value("x", 0), j.value("y", 0))
|
||
{}
|
||
|
||
/////////////////////////////////////////////////////////////////////
|
||
// for #3810
|
||
/////////////////////////////////////////////////////////////////////
|
||
|
||
struct Example_3810
|
||
{
|
||
int bla{};
|
||
|
||
Example_3810() = default;
|
||
};
|
||
|
||
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")
|
||
{
|
||
json j;
|
||
j[0]["value"] = true;
|
||
std::vector<FooBar> foo;
|
||
j.get_to(foo);
|
||
}
|
||
|
||
SECTION("issue #3108 - ordered_json doesn't support range based erase")
|
||
{
|
||
ordered_json j = {1, 2, 2, 4};
|
||
|
||
auto last = std::unique(j.begin(), j.end());
|
||
j.erase(last, j.end());
|
||
|
||
CHECK(j.dump() == "[1,2,4]");
|
||
|
||
j.erase(std::remove_if(j.begin(), j.end(), [](const ordered_json & val)
|
||
{
|
||
return val == 2;
|
||
}), j.end());
|
||
|
||
CHECK(j.dump() == "[1,4]");
|
||
}
|
||
|
||
SECTION("issue #3343 - json and ordered_json are not interchangeable")
|
||
{
|
||
json::object_t jobj({ { "product", "one" } });
|
||
ordered_json::object_t ojobj({{"product", "one"}});
|
||
|
||
auto jit = jobj.begin();
|
||
auto ojit = ojobj.begin();
|
||
|
||
CHECK(jit->first == ojit->first);
|
||
CHECK(jit->second.get<std::string>() == ojit->second.get<std::string>());
|
||
}
|
||
|
||
SECTION("issue #3171 - if class is_constructible from std::string wrong from_json overload is being selected, compilation failed")
|
||
{
|
||
const json j{{ "str", "value"}};
|
||
|
||
// failed with: error: no match for ‘operator=’ (operand types are ‘for_3171_derived’ and ‘const nlohmann::basic_json<>::string_t’
|
||
// {aka ‘const std::__cxx11::basic_string<char>’})
|
||
// s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
|
||
auto td = j.get<for_3171_derived>();
|
||
|
||
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)
|
||
const for_3204_bar bar_from_json([](json) noexcept {}); // NOLINT(performance-unnecessary-value-param)
|
||
|
||
CHECK(bar_from_foo.constructed_from == for_3204_bar::constructed_from_foo);
|
||
CHECK(bar_from_json.constructed_from == for_3204_bar::constructed_from_json);
|
||
}
|
||
|
||
SECTION("issue #3333 - Ambiguous conversion from nlohmann::basic_json<> to custom class")
|
||
{
|
||
const json j
|
||
{
|
||
{"x", 1},
|
||
{"y", 2}
|
||
};
|
||
const for_3333 p = j;
|
||
|
||
CHECK(p.x == 1);
|
||
CHECK(p.y == 2);
|
||
}
|
||
|
||
SECTION("issue #3810 - ordered_json doesn't support construction from C array of custom type")
|
||
{
|
||
Example_3810 states[45]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||
|
||
// fix "not used" warning
|
||
states[0].bla = 1;
|
||
|
||
const auto* const expected = R"([{"bla":1},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0},{"bla":0}])";
|
||
|
||
// This works:
|
||
nlohmann::json j;
|
||
j["test"] = states;
|
||
CHECK(j["test"].dump() == expected);
|
||
|
||
// This doesn't compile:
|
||
nlohmann::ordered_json oj;
|
||
oj["test"] = states;
|
||
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.
|
||
SECTION("issue #4762 - json exception 302 with unhelpful explanation : type must be number, but is number")
|
||
{
|
||
// In #4762, the main issue was that a json object with an invalid type
|
||
// returned "number" as type_name(), because this was the default case.
|
||
// This test makes sure we now return "invalid" instead.
|
||
json j;
|
||
j.m_data.m_type = static_cast<json::value_t>(100); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
|
||
CHECK(j.type_name() == "invalid");
|
||
}
|
||
#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_RANGES && !defined(__MINGW32__)
|
||
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_RANGES && !defined(__MINGW32__) && !(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_RANGES && !defined(__MINGW32__)
|
||
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)
|
||
{
|
||
// With issue #4798, we encode NaN, infinity, and -infinity as float instead
|
||
// of double to allow for smaller encodings.
|
||
const json jx = std::numeric_limits<T>::quiet_NaN();
|
||
const json jy = std::numeric_limits<T>::infinity();
|
||
const json jz = -std::numeric_limits<T>::infinity();
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
// MessagePack
|
||
/////////////////////////////////////////////////////////////////////////
|
||
|
||
// expected MessagePack values
|
||
const std::vector<std::uint8_t> msgpack_x = {{0xCA, 0x7F, 0xC0, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_y = {{0xCA, 0x7F, 0x80, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_z = {{0xCA, 0xFF, 0x80, 0x00, 0x00}};
|
||
|
||
CHECK(json::to_msgpack(jx) == msgpack_x);
|
||
CHECK(json::to_msgpack(jy) == msgpack_y);
|
||
CHECK(json::to_msgpack(jz) == msgpack_z);
|
||
|
||
CHECK(std::isnan(json::from_msgpack(msgpack_x).get<T>()));
|
||
CHECK(json::from_msgpack(msgpack_y).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_msgpack(msgpack_z).get<T>() == -std::numeric_limits<T>::infinity());
|
||
|
||
// Make sure the other MessagePakc encodings for NaN, infinity, and
|
||
// -infinity are still supported.
|
||
const std::vector<std::uint8_t> msgpack_x_2 = {{0xCB, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_y_2 = {{0xCB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> msgpack_z_2 = {{0xCB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
CHECK(std::isnan(json::from_msgpack(msgpack_x_2).get<T>()));
|
||
CHECK(json::from_msgpack(msgpack_y_2).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_msgpack(msgpack_z_2).get<T>() == -std::numeric_limits<T>::infinity());
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
// CBOR
|
||
/////////////////////////////////////////////////////////////////////////
|
||
|
||
// expected CBOR values
|
||
const std::vector<std::uint8_t> cbor_x = {{0xF9, 0x7E, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_y = {{0xF9, 0x7C, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_z = {{0xF9, 0xfC, 0x00}};
|
||
|
||
CHECK(json::to_cbor(jx) == cbor_x);
|
||
CHECK(json::to_cbor(jy) == cbor_y);
|
||
CHECK(json::to_cbor(jz) == cbor_z);
|
||
|
||
CHECK(std::isnan(json::from_cbor(cbor_x).get<T>()));
|
||
CHECK(json::from_cbor(cbor_y).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_cbor(cbor_z).get<T>() == -std::numeric_limits<T>::infinity());
|
||
|
||
// Make sure the other CBOR encodings for NaN, infinity, and -infinity are
|
||
// still supported.
|
||
const std::vector<std::uint8_t> cbor_x_2 = {{0xFA, 0x7F, 0xC0, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_y_2 = {{0xFA, 0x7F, 0x80, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_z_2 = {{0xFA, 0xFF, 0x80, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_x_3 = {{0xFB, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_y_3 = {{0xFB, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
const std::vector<std::uint8_t> cbor_z_3 = {{0xFB, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||
CHECK(std::isnan(json::from_cbor(cbor_x_2).get<T>()));
|
||
CHECK(json::from_cbor(cbor_y_2).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_cbor(cbor_z_2).get<T>() == -std::numeric_limits<T>::infinity());
|
||
CHECK(std::isnan(json::from_cbor(cbor_x_3).get<T>()));
|
||
CHECK(json::from_cbor(cbor_y_3).get<T>() == std::numeric_limits<T>::infinity());
|
||
CHECK(json::from_cbor(cbor_z_3).get<T>() == -std::numeric_limits<T>::infinity());
|
||
}
|
||
|
||
TEST_CASE("regression test #5074 - portable workaround for single-element brace init")
|
||
{
|
||
json const j_obj = {{"key", "value"}};
|
||
|
||
json const j = json::array({j_obj});
|
||
CHECK(j.is_array());
|
||
CHECK(j.size() == 1);
|
||
CHECK(j[0] == j_obj);
|
||
}
|
||
|
||
struct Example_5122
|
||
{
|
||
float b = 2;
|
||
nlohmann::ordered_map<std::string, std::string> c{}; // NOLINT(readability-redundant-member-init): needed for GCC -Weffc++
|
||
int a = 1;
|
||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Example_5122, b, c, a)
|
||
};
|
||
|
||
TEST_CASE("regression test #5122 - from_json into types holding nlohmann::ordered_map")
|
||
{
|
||
Example_5122 src;
|
||
src.c.emplace("first", "1");
|
||
src.c.emplace("second", "2");
|
||
|
||
ordered_json const j = src;
|
||
Example_5122 const dst = j.get<Example_5122>();
|
||
|
||
CHECK(dst.b == src.b);
|
||
CHECK(dst.a == src.a);
|
||
REQUIRE(dst.c.size() == src.c.size());
|
||
auto src_it = src.c.begin();
|
||
auto dst_it = dst.c.begin();
|
||
for (; src_it != src.c.end(); ++src_it, ++dst_it)
|
||
{
|
||
CHECK(dst_it->first == src_it->first);
|
||
CHECK(dst_it->second == src_it->second);
|
||
}
|
||
}
|
||
|
||
// -Wself-assign-overloaded was introduced in Clang 7. Gate the pragma on
|
||
// __has_warning so older Clang versions do not error with "unknown warning
|
||
// group". The __has_warning check has to stay inside the __clang__ branch
|
||
// because GCC does not provide it and would tokenize-error on the argument.
|
||
#if defined(__clang__) && defined(__has_warning)
|
||
#if __has_warning("-Wself-assign-overloaded")
|
||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wself-assign-overloaded")
|
||
#endif
|
||
#endif
|
||
|
||
TEST_CASE("regression test #5122 - nlohmann::ordered_map copy-assignment is self-assignment safe")
|
||
{
|
||
nlohmann::ordered_map<std::string, std::string> m;
|
||
m.emplace("first", "1");
|
||
m.emplace("second", "2");
|
||
|
||
// Insertion order is preserved by ordered_map, so we can check it directly.
|
||
m = m;
|
||
|
||
REQUIRE(m.size() == 2);
|
||
auto it = m.begin();
|
||
CHECK(it->first == "first");
|
||
CHECK(it->second == "1");
|
||
++it;
|
||
CHECK(it->first == "second");
|
||
CHECK(it->second == "2");
|
||
}
|
||
|
||
#if defined(__clang__) && defined(__has_warning)
|
||
#if __has_warning("-Wself-assign-overloaded")
|
||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||
#endif
|
||
#endif
|
||
|
||
TEST_CASE("regression test #5122 - nlohmann::ordered_map move-assignment transfers contents")
|
||
{
|
||
nlohmann::ordered_map<std::string, std::string> src;
|
||
src.emplace("first", "1");
|
||
src.emplace("second", "2");
|
||
|
||
nlohmann::ordered_map<std::string, std::string> dst;
|
||
dst.emplace("stale", "x");
|
||
dst = std::move(src);
|
||
|
||
REQUIRE(dst.size() == 2);
|
||
auto it = dst.begin();
|
||
CHECK(it->first == "first");
|
||
CHECK(it->second == "1");
|
||
++it;
|
||
CHECK(it->first == "second");
|
||
CHECK(it->second == "2");
|
||
|
||
// Re-assigning into the moved-from object must leave it in a usable state.
|
||
src = nlohmann::ordered_map<std::string, std::string> {};
|
||
src.emplace("after-move", "3");
|
||
REQUIRE(src.size() == 1);
|
||
CHECK(src.begin()->first == "after-move");
|
||
}
|
||
|
||
// Stand-in for a third-party library (e.g., Eigen as of 3.4, which added
|
||
// STL-compatible begin()/end() to its vector types), living in its own
|
||
// namespace with its own to_json overload for its vector type.
|
||
namespace issue_4320_eigen
|
||
{
|
||
// "array-compatible" from the library's point of view (it has begin()/end()),
|
||
// but for which this (fake) third-party namespace provides its own to_json.
|
||
struct vector3
|
||
{
|
||
double v[3]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-use-default-member-init,modernize-use-default-member-init)
|
||
vector3(double x, double y, double z) : v{x, y, z} {} // NOLINT(hicpp-member-init,cppcoreguidelines-pro-type-member-init)
|
||
double x() const
|
||
{
|
||
return v[0];
|
||
}
|
||
double y() const
|
||
{
|
||
return v[1];
|
||
}
|
||
double z() const
|
||
{
|
||
return v[2];
|
||
}
|
||
double* begin()
|
||
{
|
||
return v;
|
||
}
|
||
double* end()
|
||
{
|
||
return v + 3;
|
||
}
|
||
const double* begin() const
|
||
{
|
||
return v;
|
||
}
|
||
const double* end() const
|
||
{
|
||
return v + 3;
|
||
}
|
||
};
|
||
|
||
inline void to_json(json& j, const vector3& v) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
j = {{"x", v.x()}, {"y", v.y()}, {"z", v.z()}};
|
||
}
|
||
} // namespace issue_4320_eigen
|
||
|
||
// The user's own namespace, using the (fake) Eigen type as an implementation
|
||
// detail behind a payload type that has nothing to do with vectors/arrays.
|
||
namespace issue_4320
|
||
{
|
||
// Publicly derives from issue_4320_eigen::vector3 but does *not* define its
|
||
// own to_json - it is only ever used as a temporary to reach the base
|
||
// class's to_json via ADL.
|
||
struct vector3_wrapper : issue_4320_eigen::vector3
|
||
{
|
||
using issue_4320_eigen::vector3::vector3;
|
||
};
|
||
|
||
struct payload
|
||
{
|
||
double x, y, z;
|
||
};
|
||
|
||
inline vector3_wrapper to_eigen(const payload& p) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
return {p.x, p.y, p.z};
|
||
}
|
||
|
||
inline void to_json(json& j, const payload& p) // NOLINT(misc-use-internal-linkage)
|
||
{
|
||
// Unqualified call, passing a *derived* vector3_wrapper: relies on ADL
|
||
// finding issue_4320_eigen::to_json(json&, const vector3&) through the
|
||
// vector3 base class, via a derived-to-base conversion. Must NOT resolve
|
||
// to the library's own generic array-compatible to_json (an exact-match
|
||
// template for vector3_wrapper, since it also has begin()/end()), which
|
||
// would serialize this as [x, y, z] instead of {"x":x, "y":y, "z":z}.
|
||
to_json(j, to_eigen(p));
|
||
}
|
||
} // namespace issue_4320
|
||
|
||
TEST_CASE("issue #4320 - custom base class must not leak nlohmann::detail into ADL")
|
||
{
|
||
// Before the fix, basic_json unconditionally derived from a type living in
|
||
// nlohmann::detail (json_default_base), which made nlohmann::detail an
|
||
// associated namespace of every basic_json for ADL purposes. That leaked
|
||
// the library's internal generic-array to_json overload into unqualified
|
||
// to_json() calls made from user code, silently bypassing user-defined
|
||
// to_json overloads reached via a derived-to-base conversion.
|
||
const issue_4320::payload p{1.0, 2.0, 3.0};
|
||
|
||
json j;
|
||
to_json(j, p);
|
||
CHECK(j == json({{"x", 1.0}, {"y", 2.0}, {"z", 3.0}}));
|
||
}
|
||
|
||
TEST_CASE("issue #5338 - truncated CBOR tagged binary subtype is rejected")
|
||
{
|
||
const std::vector<std::vector<std::uint8_t>> truncated_tags =
|
||
{
|
||
{0xD8},
|
||
{0xD9, 0x00},
|
||
{0xDA, 0x00, 0x00, 0x00},
|
||
{0xDB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||
};
|
||
|
||
for (const auto& data : truncated_tags)
|
||
{
|
||
CAPTURE(data)
|
||
for (const auto tag_handler :
|
||
{
|
||
json::cbor_tag_handler_t::ignore, json::cbor_tag_handler_t::store
|
||
})
|
||
{
|
||
CAPTURE(tag_handler)
|
||
const auto result = json::from_cbor(data, true, false, tag_handler);
|
||
CHECK(result.is_discarded());
|
||
}
|
||
}
|
||
}
|
||
|
||
TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
|
||
{
|
||
json t = {{"k", 1}};
|
||
t.update(json{{"k", {{"x", 2}}}}, true);
|
||
CHECK(t == json({{"k", {{"x", 2}}}}));
|
||
|
||
json mixed = {{"keep", {{"a", 1}}}, {"replace", 1}};
|
||
mixed.update(json{{"keep", {{"b", 2}}}, {"replace", {{"x", 2}}}}, true);
|
||
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
|
||
// array type to have a reserve() member function
|
||
using deque_json = nlohmann::basic_json<std::map, std::deque>;
|
||
|
||
SECTION("std::deque")
|
||
{
|
||
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
|
||
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
|
||
|
||
// the binary formats pass a definite length to start_array()
|
||
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
||
CHECK(deque_json::from_msgpack(deque_json::to_msgpack(j)) == j);
|
||
CHECK(deque_json::from_bon8(deque_json::to_bon8(j)) == j);
|
||
|
||
// parse() instantiates the callback parser as well, which reserves too
|
||
const auto with_callback = deque_json::parse(R"([1,2,3])", [](int /*depth*/, deque_json::parse_event_t /*event*/, deque_json& /*parsed*/) noexcept
|
||
{
|
||
return true;
|
||
});
|
||
CHECK(with_callback == deque_json({1, 2, 3}));
|
||
}
|
||
|
||
SECTION("std::vector still reserves")
|
||
{
|
||
json array = json::array();
|
||
for (int i = 0; i < 100; ++i)
|
||
{
|
||
array.push_back(i);
|
||
}
|
||
|
||
const auto j = json::from_cbor(json::to_cbor(array));
|
||
CHECK(j == array);
|
||
CHECK(j.get_ref<const json::array_t&>().capacity() >= 100);
|
||
}
|
||
|
||
SECTION("the reservation stays capped")
|
||
{
|
||
// CBOR array announcing 2^32-1 elements, but truncated right after the
|
||
// header: the input must be rejected without reserving that capacity
|
||
const std::vector<std::uint8_t> truncated = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
|
||
CHECK(json::from_cbor(truncated, true, false).is_discarded());
|
||
}
|
||
}
|
||
|
||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|