mirror of
https://github.com/nlohmann/json.git
synced 2026-10-02 06:55:16 +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>
870 lines
30 KiB
C++
870 lines
30 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_DISABLE_TUPLE_REFERENCE_CONVERSION=1 (#2226)
|
|
#if defined(JSON_DISABLE_TUPLE_REFERENCE_CONVERSION) && (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION == 1)
|
|
#define SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
|
#endif
|
|
|
|
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
|
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
|
// the tuple constructors make them instantiate basic_json's conversion operator
|
|
// for libstdc++'s internal tuple bases, which fails hard
|
|
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
|
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
#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 <list>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#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::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
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #4825 - explicitly instantiating basic_json must compile; this
|
|
// forces instantiation of binary_writer::write_bjdata_ndarray, whose
|
|
// static_cast<string_t> was ambiguous under explicit instantiation on
|
|
// C++17. Merely compiling this translation unit is the regression test.
|
|
/////////////////////////////////////////////////////////////////////
|
|
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")
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #1021
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #1647
|
|
/////////////////////////////////////////////////////////////////////
|
|
namespace
|
|
{
|
|
struct NonDefaultFromJsonStruct
|
|
{};
|
|
|
|
inline bool operator==(NonDefaultFromJsonStruct const& /*unused*/, NonDefaultFromJsonStruct const& /*unused*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
enum class for_1647
|
|
{
|
|
one,
|
|
two
|
|
};
|
|
|
|
// NOLINTNEXTLINE(misc-const-correctness): this is a false positive
|
|
NLOHMANN_JSON_SERIALIZE_ENUM(for_1647,
|
|
{
|
|
{for_1647::one, "one"},
|
|
{for_1647::two, "two"},
|
|
})
|
|
} // namespace
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #1299
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
struct Data
|
|
{
|
|
Data() = default;
|
|
Data(std::string a_, std::string b_)
|
|
: a(std::move(a_))
|
|
, b(std::move(b_))
|
|
{}
|
|
std::string a{}; // NOLINT(readability-redundant-member-init)
|
|
std::string b{}; // NOLINT(readability-redundant-member-init)
|
|
};
|
|
|
|
void from_json(const json& j, Data& data); // NOLINT(misc-use-internal-linkage)
|
|
void from_json(const json& j, Data& data)
|
|
{
|
|
j["a"].get_to(data.a);
|
|
j["b"].get_to(data.b);
|
|
}
|
|
|
|
bool operator==(Data const& lhs, Data const& rhs); // NOLINT(misc-use-internal-linkage)
|
|
bool operator==(Data const& lhs, Data const& rhs)
|
|
{
|
|
return lhs.a == rhs.a && lhs.b == rhs.b;
|
|
}
|
|
|
|
//bool operator!=(Data const& lhs, Data const& rhs)
|
|
//{
|
|
// return !(lhs == rhs);
|
|
//}
|
|
|
|
namespace nlohmann
|
|
{
|
|
template<>
|
|
struct adl_serializer<NonDefaultFromJsonStruct>
|
|
{
|
|
static NonDefaultFromJsonStruct from_json(json const& /*unused*/) noexcept
|
|
{
|
|
return {};
|
|
}
|
|
};
|
|
} // namespace nlohmann
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #1805
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
struct NotSerializableData
|
|
{
|
|
int mydata;
|
|
float myfloat;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #2574
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
struct NonDefaultConstructible
|
|
{
|
|
explicit NonDefaultConstructible(int a)
|
|
: x(a)
|
|
{}
|
|
int x;
|
|
};
|
|
|
|
namespace nlohmann
|
|
{
|
|
template<>
|
|
struct adl_serializer<NonDefaultConstructible>
|
|
{
|
|
static NonDefaultConstructible from_json(json const& j)
|
|
{
|
|
return NonDefaultConstructible(j.get<int>());
|
|
}
|
|
};
|
|
} // namespace nlohmann
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// for #2982
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
template<class T>
|
|
class my_allocator : public std::allocator<T>
|
|
{
|
|
public:
|
|
using std::allocator<T>::allocator;
|
|
|
|
my_allocator() = default;
|
|
template<class U> my_allocator(const my_allocator<U>& /*unused*/) { }
|
|
|
|
template <class U>
|
|
struct rebind
|
|
{
|
|
using other = my_allocator<U>;
|
|
};
|
|
};
|
|
|
|
TEST_CASE("regression tests 2")
|
|
{
|
|
SECTION("issue #1001 - Fix memory leak during parser callback")
|
|
{
|
|
const auto* geojsonExample = R"(
|
|
{ "type": "FeatureCollection",
|
|
"features": [
|
|
{ "type": "Feature",
|
|
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
|
|
"properties": {"prop0": "value0"}
|
|
},
|
|
{ "type": "Feature",
|
|
"geometry": {
|
|
"type": "LineString",
|
|
"coordinates": [
|
|
[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
|
|
]
|
|
},
|
|
"properties": {
|
|
"prop0": "value0",
|
|
"prop1": 0.0
|
|
}
|
|
},
|
|
{ "type": "Feature",
|
|
"geometry": {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
|
|
[100.0, 1.0], [100.0, 0.0] ]
|
|
]
|
|
},
|
|
"properties": {
|
|
"prop0": "value0",
|
|
"prop1": {"this": "that"}
|
|
}
|
|
}
|
|
]
|
|
})";
|
|
|
|
const json::parser_callback_t cb = [&](int /*level*/, json::parse_event_t event, json & parsed) noexcept
|
|
{
|
|
// skip uninteresting events
|
|
if (event == json::parse_event_t::value && !parsed.is_primitive())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (event)
|
|
{
|
|
case json::parse_event_t::key:
|
|
{
|
|
return true;
|
|
}
|
|
case json::parse_event_t::value:
|
|
{
|
|
return false;
|
|
}
|
|
case json::parse_event_t::object_start:
|
|
{
|
|
return true;
|
|
}
|
|
case json::parse_event_t::object_end:
|
|
{
|
|
return false;
|
|
}
|
|
case json::parse_event_t::array_start:
|
|
{
|
|
return true;
|
|
}
|
|
case json::parse_event_t::array_end:
|
|
{
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
auto j = json::parse(geojsonExample, cb, true);
|
|
CHECK(j == json());
|
|
}
|
|
|
|
SECTION("issue #1021 - to/from_msgpack only works with standard typization")
|
|
{
|
|
float_json j = 1000.0;
|
|
CHECK(float_json::from_cbor(float_json::to_cbor(j)) == j);
|
|
CHECK(float_json::from_msgpack(float_json::to_msgpack(j)) == j);
|
|
CHECK(float_json::from_ubjson(float_json::to_ubjson(j)) == j);
|
|
CHECK(float_json::from_bon8(float_json::to_bon8(j)) == j);
|
|
|
|
float_json j2 = {1000.0, 2000.0, 3000.0};
|
|
CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
|
|
}
|
|
|
|
SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?")
|
|
{
|
|
json diffs = nlohmann::json::array();
|
|
json m1{{"key1", 42}};
|
|
json m2{{"key2", 42}};
|
|
auto p1 = m1.items();
|
|
auto p2 = m2.items();
|
|
|
|
using it_type = decltype(p1.begin());
|
|
|
|
std::set_difference(
|
|
p1.begin(),
|
|
p1.end(),
|
|
p2.begin(),
|
|
p2.end(),
|
|
std::inserter(diffs, diffs.end()),
|
|
[&](const it_type & e1, const it_type & e2) -> bool
|
|
{
|
|
using comper_pair = std::pair<std::string, decltype(e1.value())>; // Trying to avoid unneeded copy
|
|
return comper_pair(e1.key(), e1.value()) < comper_pair(e2.key(), e2.value()); // Using pair comper
|
|
});
|
|
|
|
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")
|
|
{
|
|
const json j =
|
|
{
|
|
{"1", {{"a", "testa_1"}, {"b", "testb_1"}}},
|
|
{"2", {{"a", "testa_2"}, {"b", "testb_2"}}},
|
|
{"3", {{"a", "testa_3"}, {"b", "testb_3"}}},
|
|
};
|
|
|
|
const std::map<std::string, Data> expected
|
|
{
|
|
{"1", {"testa_1", "testb_1"}},
|
|
{"2", {"testa_2", "testb_2"}},
|
|
{"3", {"testa_3", "testb_3"}},
|
|
};
|
|
const auto data = j.get<decltype(expected)>();
|
|
CHECK(expected == data);
|
|
}
|
|
|
|
SECTION("issue #1445 - buffer overflow in dumping invalid utf-8 strings")
|
|
{
|
|
SECTION("a bunch of -1, ensure_ascii=true")
|
|
{
|
|
const auto length = 300;
|
|
|
|
json dump_test;
|
|
dump_test["1"] = std::string(length, static_cast<std::string::value_type>(-1));
|
|
|
|
std::string expected = R"({"1":")";
|
|
for (int i = 0; i < length; ++i)
|
|
{
|
|
expected += "\\ufffd";
|
|
}
|
|
expected += "\"}";
|
|
|
|
auto s = dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
|
|
CHECK(s == expected);
|
|
}
|
|
SECTION("a bunch of -2, ensure_ascii=false")
|
|
{
|
|
const auto length = 500;
|
|
|
|
json dump_test;
|
|
dump_test["1"] = std::string(length, static_cast<std::string::value_type>(-2));
|
|
|
|
std::string expected = R"({"1":")";
|
|
for (int i = 0; i < length; ++i)
|
|
{
|
|
expected += "\xEF\xBF\xBD";
|
|
}
|
|
expected += "\"}";
|
|
|
|
auto s = dump_test.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
|
|
CHECK(s == expected);
|
|
}
|
|
SECTION("test case in issue #1445")
|
|
{
|
|
nlohmann::json dump_test;
|
|
const std::array<int, 108> data =
|
|
{
|
|
{109, 108, 103, 125, -122, -53, 115, 18, 3, 0, 102, 19, 1, 15, -110, 13, -3, -1, -81, 32, 2, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -80, 2, 0, 0, 96, -118, 46, -116, 46, 109, -84, -87, 108, 14, 109, -24, -83, 13, -18, -51, -83, -52, -115, 14, 6, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 3, 0, 0, 0, 35, -74, -73, 55, 57, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, -96, -54, -28, -26}
|
|
};
|
|
std::string s;
|
|
for (const int i : data)
|
|
{
|
|
s += static_cast<char>(i);
|
|
}
|
|
dump_test["1"] = s;
|
|
// dump() is nodiscard; this only checks that dumping does not throw/crash
|
|
utils::ignore_return_value(dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace));
|
|
}
|
|
}
|
|
|
|
SECTION("issue #1447 - Integer Overflow (OSS-Fuzz 12506)")
|
|
{
|
|
const json j = json::parse("[-9223372036854775808]");
|
|
CHECK(j.dump() == "[-9223372036854775808]");
|
|
}
|
|
|
|
SECTION("issue #1708 - minimum value of int64_t can be outputted")
|
|
{
|
|
constexpr auto smallest = (std::numeric_limits<int64_t>::min)();
|
|
const json j = smallest;
|
|
CHECK(j.dump() == std::to_string(smallest));
|
|
}
|
|
|
|
SECTION("issue #1727 - Contains with non-const lvalue json_pointer picks the wrong overload")
|
|
{
|
|
const json j = {{"root", {{"settings", {{"logging", true}}}}}};
|
|
|
|
auto jptr1 = "/root/settings/logging"_json_pointer;
|
|
auto jptr2 = json::json_pointer{"/root/settings/logging"};
|
|
|
|
CHECK(j.contains(jptr1));
|
|
CHECK(j.contains(jptr2));
|
|
}
|
|
|
|
SECTION("issue #1647 - compile error when deserializing enum if both non-default from_json and non-member operator== exists for other type")
|
|
{
|
|
// does not compile on ICPC when targeting C++20
|
|
#if !(defined(__INTEL_COMPILER) && __cplusplus >= 202000)
|
|
{
|
|
const json j;
|
|
const NonDefaultFromJsonStruct x(j);
|
|
NonDefaultFromJsonStruct y;
|
|
CHECK(x == y);
|
|
}
|
|
#endif
|
|
|
|
auto val = nlohmann::json("one").get<for_1647>();
|
|
CHECK(val == for_1647::one);
|
|
const json j = val;
|
|
}
|
|
|
|
SECTION("issue #1715 - json::from_cbor does not respect allow_exceptions = false when input is string literal")
|
|
{
|
|
SECTION("string literal")
|
|
{
|
|
const json cbor = json::from_cbor("B", true, false);
|
|
CHECK(cbor.is_discarded());
|
|
}
|
|
|
|
SECTION("string array")
|
|
{
|
|
const std::array<char, 2> input = {{'B', 0x00}};
|
|
const json cbor = json::from_cbor(input, true, false);
|
|
CHECK(cbor.is_discarded());
|
|
}
|
|
|
|
SECTION("std::string")
|
|
{
|
|
const json cbor = json::from_cbor(std::string("B"), true, false);
|
|
CHECK(cbor.is_discarded());
|
|
}
|
|
}
|
|
|
|
SECTION("issue #1805 - A pair<T1, T2> is json constructible only if T1 and T2 are json constructible")
|
|
{
|
|
static_assert(!std::is_constructible<json, std::pair<std::string, NotSerializableData>>::value, "unexpected result");
|
|
static_assert(!std::is_constructible<json, std::pair<NotSerializableData, std::string>>::value, "unexpected result");
|
|
static_assert(std::is_constructible<json, std::pair<int, std::string>>::value, "unexpected result");
|
|
}
|
|
SECTION("issue #1825 - A tuple<Args..> is json constructible only if all T in Args are json constructible")
|
|
{
|
|
static_assert(!std::is_constructible<json, std::tuple<std::string, NotSerializableData>>::value, "unexpected result");
|
|
static_assert(!std::is_constructible<json, std::tuple<NotSerializableData, std::string>>::value, "unexpected result");
|
|
static_assert(std::is_constructible<json, std::tuple<int, std::string>>::value, "unexpected result");
|
|
}
|
|
|
|
SECTION("issue #1983 - JSON patch diff for op=add formation is not as per standard (RFC 6902)")
|
|
{
|
|
const auto source = R"({ "foo": [ "1", "2" ] })"_json;
|
|
const auto target = R"({"foo": [ "1", "2", "3" ]})"_json;
|
|
const auto result = json::diff(source, target);
|
|
CHECK(result.dump() == R"([{"op":"add","path":"/foo/-","value":"3"}])");
|
|
}
|
|
|
|
SECTION("issue #2067 - cannot serialize binary data to text JSON")
|
|
{
|
|
const std::array<unsigned char, 23> data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}};
|
|
const json j = json::from_msgpack(data.data(), data.size());
|
|
// dump() is nodiscard; this only checks that dumping does not throw
|
|
CHECK_NOTHROW(
|
|
utils::ignore_return_value(
|
|
j.dump(4, // Indent
|
|
' ', // Indent char
|
|
false, // Ensure ascii
|
|
json::error_handler_t::strict // Error
|
|
)));
|
|
}
|
|
|
|
#ifndef SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
|
SECTION("issue #2226 - std::tuple dangling reference - implicit conversion")
|
|
{
|
|
// by default, a one-element tuple holding a json reference converts to
|
|
// a one-element array; JSON_DISABLE_TUPLE_REFERENCE_CONVERSION removes
|
|
// this conversion (see unit-disable-tuple-reference-conversion.cpp)
|
|
const json j = true;
|
|
CHECK(std::is_constructible<json, std::tuple<const json&>>::value);
|
|
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
CHECK(json(std::forward_as_tuple(j)) == json::array({true}));
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
SECTION("PR #2181 - regression bug with lvalue")
|
|
{
|
|
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
|
const json j{{"x", "test"}};
|
|
const std::string defval = "default value";
|
|
auto val = j.value("x", defval); // NOLINT(bugprone-unused-local-non-trivial-variable)
|
|
auto val2 = j.value("y", defval); // NOLINT(bugprone-unused-local-non-trivial-variable)
|
|
}
|
|
|
|
SECTION("issue #2293 - eof doesn't cause parsing to stop")
|
|
{
|
|
const std::vector<uint8_t> data =
|
|
{
|
|
0x7B,
|
|
0x6F,
|
|
0x62,
|
|
0x6A,
|
|
0x65,
|
|
0x63,
|
|
0x74,
|
|
0x20,
|
|
0x4F,
|
|
0x42
|
|
};
|
|
const json result = json::from_cbor(data, true, false);
|
|
CHECK(result.is_discarded());
|
|
}
|
|
|
|
SECTION("issue #2315 - json.update and vector<pair>does not work with ordered_json")
|
|
{
|
|
nlohmann::ordered_json jsonAnimals = {{"animal", "dog"}};
|
|
const nlohmann::ordered_json jsonCat = {{"animal", "cat"}};
|
|
jsonAnimals.update(jsonCat);
|
|
CHECK(jsonAnimals["animal"] == "cat");
|
|
|
|
auto jsonAnimals_parsed = nlohmann::ordered_json::parse(jsonAnimals.dump());
|
|
CHECK(jsonAnimals == jsonAnimals_parsed);
|
|
|
|
const std::vector<std::pair<std::string, int64_t>> intData = {std::make_pair("aaaa", 11),
|
|
std::make_pair("bbb", 222)
|
|
};
|
|
nlohmann::ordered_json jsonObj;
|
|
for (const auto& data : intData)
|
|
{
|
|
jsonObj[data.first] = data.second;
|
|
}
|
|
CHECK(jsonObj["aaaa"] == 11);
|
|
CHECK(jsonObj["bbb"] == 222);
|
|
}
|
|
|
|
SECTION("issue #2330 - ignore_comment=true fails on multiple consecutive lines starting with comments")
|
|
{
|
|
const std::string ss = "//\n//\n{\n}\n";
|
|
const json j = json::parse(ss, nullptr, true, true);
|
|
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")
|
|
{
|
|
{
|
|
const json j = {7, 4};
|
|
auto arr = j.get<std::array<NonDefaultConstructible, 2>>();
|
|
CHECK(arr[0].x == 7);
|
|
CHECK(arr[1].x == 4);
|
|
}
|
|
|
|
{
|
|
const json j = 7;
|
|
CHECK_THROWS_AS((j.get<std::array<NonDefaultConstructible, 1>>()), json::type_error);
|
|
}
|
|
}
|
|
|
|
SECTION("std::pair")
|
|
{
|
|
{
|
|
const json j = {3, 8};
|
|
auto p = j.get<std::pair<NonDefaultConstructible, NonDefaultConstructible>>();
|
|
CHECK(p.first.x == 3);
|
|
CHECK(p.second.x == 8);
|
|
}
|
|
|
|
{
|
|
const json j = {4, 1};
|
|
auto p = j.get<std::pair<int, NonDefaultConstructible>>();
|
|
CHECK(p.first == 4);
|
|
CHECK(p.second.x == 1);
|
|
}
|
|
|
|
{
|
|
const json j = {6, 7};
|
|
auto p = j.get<std::pair<NonDefaultConstructible, int>>();
|
|
CHECK(p.first.x == 6);
|
|
CHECK(p.second == 7);
|
|
}
|
|
|
|
{
|
|
const json j = 7;
|
|
CHECK_THROWS_AS((j.get<std::pair<NonDefaultConstructible, int>>()), json::type_error);
|
|
}
|
|
}
|
|
|
|
SECTION("std::tuple")
|
|
{
|
|
{
|
|
const json j = {9};
|
|
auto t = j.get<std::tuple<NonDefaultConstructible>>();
|
|
CHECK(std::get<0>(t).x == 9);
|
|
}
|
|
|
|
{
|
|
const json j = {9, 8, 7};
|
|
auto t = j.get<std::tuple<NonDefaultConstructible, int, NonDefaultConstructible>>();
|
|
CHECK(std::get<0>(t).x == 9);
|
|
CHECK(std::get<1>(t) == 8);
|
|
CHECK(std::get<2>(t).x == 7);
|
|
}
|
|
|
|
{
|
|
const json j = 7;
|
|
CHECK_THROWS_AS((j.get<std::tuple<NonDefaultConstructible>>()), json::type_error);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("issue #4530 - Serialization of empty tuple")
|
|
{
|
|
const auto source_tuple = std::tuple<>();
|
|
const nlohmann::json j = source_tuple;
|
|
|
|
CHECK(j.get<decltype(source_tuple)>() == source_tuple);
|
|
CHECK("[]" == j.dump());
|
|
}
|
|
|
|
SECTION("issue #2865 - ASAN detects memory leaks")
|
|
{
|
|
// the code below is expected to not leak memory
|
|
{
|
|
nlohmann::json o;
|
|
const std::string s = "bar";
|
|
|
|
nlohmann::to_json(o["foo"], s);
|
|
|
|
nlohmann::json p = o;
|
|
|
|
// call to_json with a non-null JSON value
|
|
nlohmann::to_json(p["foo"], s);
|
|
}
|
|
|
|
{
|
|
nlohmann::json o;
|
|
const std::string s = "bar";
|
|
|
|
nlohmann::to_json(o["foo"], s);
|
|
|
|
// call to_json with a non-null JSON value
|
|
nlohmann::to_json(o["foo"], s);
|
|
}
|
|
}
|
|
|
|
SECTION("issue #2825 - Properly constrain the basic_json conversion operator")
|
|
{
|
|
static_assert(std::is_copy_assignable<nlohmann::ordered_json>::value, "ordered_json must be copy assignable");
|
|
}
|
|
|
|
SECTION("issue #2958 - Inserting in unordered json using a pointer retains the leading slash")
|
|
{
|
|
const std::string p = "/root";
|
|
|
|
json test1;
|
|
test1[json::json_pointer(p)] = json::object();
|
|
CHECK(test1.dump() == "{\"root\":{}}");
|
|
|
|
ordered_json test2;
|
|
test2[ordered_json::json_pointer(p)] = json::object();
|
|
CHECK(test2.dump() == "{\"root\":{}}");
|
|
|
|
// json::json_pointer and ordered_json::json_pointer are the same type; behave as above
|
|
ordered_json test3;
|
|
test3[json::json_pointer(p)] = json::object();
|
|
CHECK(std::is_same<json::json_pointer::string_t, ordered_json::json_pointer::string_t>::value);
|
|
CHECK(test3.dump() == "{\"root\":{}}");
|
|
}
|
|
|
|
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;
|
|
const json j = {1, 2, 3, 4};
|
|
json::to_cbor(j, my_vector);
|
|
json k = json::from_cbor(my_vector);
|
|
CHECK(j == k);
|
|
}
|
|
|
|
}
|
|
|
|
TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value")
|
|
{
|
|
// a callback that rejects only the scalar value 2
|
|
const json::parser_callback_t drop_value_2 = [](int /*depth*/, json::parse_event_t ev, json & v) noexcept
|
|
{
|
|
return !(ev == json::parse_event_t::value && v == 2);
|
|
};
|
|
|
|
SECTION("duplicate key, second (scalar) value rejected - prior value is restored")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":2})", drop_value_2);
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value is an object rejected at object_end - prior value is restored")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":{"x":2}})",
|
|
[](int depth, json::parse_event_t ev, json& /*parsed*/) noexcept
|
|
{
|
|
return !(ev == json::parse_event_t::object_end && depth == 1);
|
|
});
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value is an array rejected at array_end - prior value is restored")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":[9,9]})",
|
|
[](int depth, json::parse_event_t ev, json& /*parsed*/) noexcept
|
|
{
|
|
return !(ev == json::parse_event_t::array_end && depth == 1);
|
|
});
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value accepted (scalar) - last value wins")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":2})", [](int, json::parse_event_t, json&) noexcept
|
|
{
|
|
return true;
|
|
});
|
|
CHECK(j.dump() == "{\"a\":2}");
|
|
}
|
|
|
|
SECTION("duplicate key, second value accepted (object) - last value wins")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"a":{"x":2}})", [](int, json::parse_event_t, json&) noexcept
|
|
{
|
|
return true;
|
|
});
|
|
CHECK(j.dump() == "{\"a\":{\"x\":2}}");
|
|
}
|
|
|
|
SECTION("brand new (non-duplicate) key, value rejected - member is fully absent")
|
|
{
|
|
const json j = json::parse(R"({"a":1,"b":2})", drop_value_2);
|
|
CHECK(j.dump() == "{\"a\":1}");
|
|
}
|
|
|
|
SECTION("duplicate key nested two levels deep")
|
|
{
|
|
const json j = json::parse(R"({"outer":{"a":1,"a":2}})", drop_value_2);
|
|
CHECK(j.dump() == "{\"outer\":{\"a\":1}}");
|
|
}
|
|
|
|
SECTION("three occurrences of the same key - middle rejected, last accepted")
|
|
{
|
|
const json j = json::parse(R"({"k":1,"k":2,"k":3})", drop_value_2);
|
|
CHECK(j.dump() == "{\"k\":3}");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("regression test - excessive binary container size honors allow_exceptions=false")
|
|
{
|
|
// CBOR array with declared length 2^63
|
|
const std::vector<std::uint8_t> cbor = {0x9b, 0x80, 0, 0, 0, 0, 0, 0, 0};
|
|
// CBOR map with declared length 2^63
|
|
const std::vector<std::uint8_t> cbor_m = {0xbb, 0x80, 0, 0, 0, 0, 0, 0, 0};
|
|
// UBJSON array with declared length 2^63-1
|
|
const std::vector<std::uint8_t> ubj = {'[', '#', 'L', 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
|
// BJData array with declared length 2^63-1 (little endian)
|
|
const std::vector<std::uint8_t> bjd = {'[', '#', 'L', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f};
|
|
|
|
// allow_exceptions=false must report failure instead of throwing/aborting
|
|
CHECK(json::from_cbor(cbor, true, false).is_discarded());
|
|
CHECK(json::from_cbor(cbor_m, true, false).is_discarded());
|
|
CHECK(json::from_ubjson(ubj, true, false).is_discarded());
|
|
CHECK(json::from_bjdata(bjd, true, false).is_discarded());
|
|
|
|
// allow_exceptions=true (the default) must still throw exactly as before.
|
|
// The exact message text is not checked here: on platforms where
|
|
// std::size_t is 32-bit, the CBOR reader's own length-narrowing check
|
|
// (get_cbor_container_size(), unrelated to this fix) intercepts a
|
|
// declared length of 2^63 before it ever reaches the check this test
|
|
// targets, with different (but equally valid, and already correct)
|
|
// wording -- see unit-cbor.cpp for coverage of that message.
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(cbor), json::out_of_range);
|
|
|
|
// regression guard: a genuinely truncated CBOR input must remain discarded
|
|
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
|
}
|
|
|
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|