From 11e75a54280b3474cb18c2f24d42660dc385596d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 1 Oct 2026 10:53:10 +0200 Subject: [PATCH 01/27] Cancel CI runs of merged and closed pull requests (#5748) --- .github/workflows/cancel_closed_pr_runs.yml | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/cancel_closed_pr_runs.yml diff --git a/.github/workflows/cancel_closed_pr_runs.yml b/.github/workflows/cancel_closed_pr_runs.yml new file mode 100644 index 000000000..4d8ee33e5 --- /dev/null +++ b/.github/workflows/cancel_closed_pr_runs.yml @@ -0,0 +1,53 @@ +name: "Cancel runs of closed pull requests" + +# The concurrency groups of the other workflows cancel superseded runs when a +# pull request gets new commits, but nothing stops the runs of its last commit +# once the pull request is merged or closed. They then keep the runners busy +# for hours while the queue of the open pull requests waits. +# +# pull_request_target is needed to get a token that can cancel runs for pull +# requests from forks. This is safe because the workflow never checks out or +# runs code from the pull request; it only calls the API. +on: + pull_request_target: + types: [closed] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + cancel: + permissions: + actions: write + + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 + with: + egress-policy: audit + + - name: Cancel unfinished runs of the pull request's head commit + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + SELF: ${{ github.run_id }} + # only runs triggered by the pull request: when a branch is pushed to + # develop directly, its push runs share the head commit + run: | + gh api --paginate "repos/$GH_REPO/actions/runs?head_sha=$HEAD_SHA&per_page=100" \ + --jq ".workflow_runs[] + | select(.status != \"completed\" and .id != $SELF) + | select(.event == \"pull_request\" or .event == \"pull_request_target\") + | \"\(.id) \(.name)\"" | + while read -r id name; do + echo "Cancelling run $id ($name)" + # a run may finish between listing and cancelling; that is not an error + gh run cancel "$id" || true + done From 6aec1a085f3674508040970cbcac6c72f6418793 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 2 Oct 2026 07:47:50 +0200 Subject: [PATCH 02/27] Report BON8 input that ends after a UTF-8 lead byte as truncated (#5677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A lead byte (0xC2..0xF7) inside a string begins either another character (if a continuation byte follows) or an integer (otherwise). When the input ended right after the lead byte, the reader took the missing byte as "not a continuation byte", ended the string before the lead byte, and treated the lead byte as the start of the next value. With strict=false, a message cut off there was therefore read as a shorter value: the 11 bytes of "šŸ˜€šŸ˜€Ć©" cut after 9 bytes gave "šŸ˜€šŸ˜€", and ["aĆ©"] cut after 3 of its 5 bytes gave ["a"]. With strict=true, the input was rejected with a misleading message ("expected end of input"), or, for a key, with parse_error.112 instead of 110. Either reading of the lead byte leaves the message incomplete: a string at the end of a message must be terminated by 0xFF, so the lead byte cannot belong to a following message. Report parse_error.110 (unexpected end of input) for strings and keys, as the comment on get_bon8_string() already requires and as the reference decoder (HikoGUI) does. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 11 +++++ single_include/nlohmann/json.hpp | 11 +++++ tests/src/unit-bon8.cpp | 41 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 9060d76a9..18511071f 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -3688,6 +3688,11 @@ class binary_reader if (0xC2 <= byte && byte <= 0xF7) { const auto second = get_bon8(); + if (second == char_traits::eof()) + { + // the input ends inside a character or an integer + return unexpect_eof(input_format_t::bon8, "key"); + } unget_bon8(second); if (is_bon8_continuation(second)) { @@ -3786,6 +3791,12 @@ class binary_reader // a lead byte ends the string if no continuation byte follows: it // is then the first byte of an integer const auto second = get_bon8(); + if (second == char_traits::eof()) + { + // the input ends inside a character or an integer: either + // way, the message is incomplete + return unexpect_eof(input_format_t::bon8, "string"); + } if (!is_bon8_continuation(second)) { unget_bon8(second); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0e394cb10..e093e4ece 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -17201,6 +17201,11 @@ class binary_reader if (0xC2 <= byte && byte <= 0xF7) { const auto second = get_bon8(); + if (second == char_traits::eof()) + { + // the input ends inside a character or an integer + return unexpect_eof(input_format_t::bon8, "key"); + } unget_bon8(second); if (is_bon8_continuation(second)) { @@ -17299,6 +17304,12 @@ class binary_reader // a lead byte ends the string if no continuation byte follows: it // is then the first byte of an integer const auto second = get_bon8(); + if (second == char_traits::eof()) + { + // the input ends inside a character or an integer: either + // way, the message is incomplete + return unexpect_eof(input_format_t::bon8, "string"); + } if (!is_bon8_continuation(second)) { unget_bon8(second); diff --git a/tests/src/unit-bon8.cpp b/tests/src/unit-bon8.cpp index 94aba538f..a751d7dbe 100644 --- a/tests/src/unit-bon8.cpp +++ b/tests/src/unit-bon8.cpp @@ -459,6 +459,47 @@ TEST_CASE("BON8") CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&); } + SECTION("input that ends after a UTF-8 lead byte") + { + // the lead byte begins either a character or an integer; both are + // incomplete, so the lead byte must not end the string before it + for (const bool strict : + { + true, false + }) + { + CAPTURE(strict) + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xE2}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xF0}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 0xC3, 0xA9, 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a', 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xC3}, strict), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x88, 'a', 0x91, 0xE2}, strict), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&); + } + } + + SECTION("a message that is cut off is not read as a shorter value") + { + const json values = {"\xC3\xA9", "a\xE2\x82\xAC", "\xF0\x9F\x98\x80\xC3\xA9", {"a\xC3\xA9"}, {{"\xC3\xA9", "\xE2\x82\xAC"}}, {{"a", {"b\xC3\xA9", 1}}}}; + for (const auto& j : values) + { + const bytes message = json::to_bon8(j); + for (std::size_t length = 0; length < message.size(); ++length) + { + CAPTURE(j) + CAPTURE(length) + bytes prefix = message; + prefix.resize(length); + CHECK(json::from_bon8(prefix, false, false).is_discarded()); + // a stream is read byte by byte rather than in bulk + std::istringstream stream(str(prefix)); + CHECK(json::from_bon8(stream, false, false).is_discarded()); + } + } + } + SECTION("invalid UTF-8") { // overlong From 4d46bce4e14112964cc0653b3b677cc7eac90a4d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 2 Oct 2026 11:23:55 +0200 Subject: [PATCH 03/27] Fix CI on develop (#5750) * Fix CI configuration broken by recent merges and tool updates - gcc_flags.cmake: drop -Wexperimental-fmv-target, which GCC 16 accepts only on aarch64; amd64 rejects it, so every GCC job failed while checking the compiler. - ci_get_cmake: add VERBATIM so the checksum pipeline is passed to the shell intact (the unescaped `$'` broke the generated Makefile and build.ninja, failing ci_cmake_flags and ci_module_cpp20); match the SHA-256 entry case-insensitively, as CMake 3.5.0 lists the archive as "Linux-x86_64"; and unpack with --strip-components, as that archive's top-level directory is spelled "Linux" too. - ci_single_binaries: compile json.hpp's TU without IWYU's --error, as the comment above the gate already intends. - tests: restore -Wno-deprecated-declarations for all non-MSVC compilers (#5737 kept it for GCC only), as several tests call deprecated functions on purpose; include thirdparty/fifo_map as SYSTEM. - .clang-tidy: set misc-use-internal-linkage.AnalyzeTypes to false; clang-tidy 22.1 extended the check to classes and enums and flagged 100 test helper types. Signed-off-by: Niels Lohmann * Fix library warnings and a JSON_DIAGNOSTICS parent bug - binary_reader: pass integers to sax->number_integer() through conditional_static_cast, making the existing narrowing for a narrow number_integer_t explicit (MSVC C4244 and GCC -Wconversion/-Warith-conversion with the int16_t test from #5694); mark two Infer DEAD_STORE false positives with @infer-ignore. - to_json: set the parents of an array built from a C++20 range view after all elements are in place; a reallocating push_back moved the earlier elements and left their parent pointers stale, failing the JSON_DIAGNOSTICS invariant assertion. - json.hpp: suppress MSVC C4127 for the new is_ordered_map check in diff(), like the three existing ones; spell out std::formatter::parse's return and iterator types for clang-tidy 22.1. - number_parse: make the Eisel-Lemire digit counter unsigned (GCC -Wstrict-overflow). - string_utils: take encode_utf8's callable by const reference (cppcoreguidelines-missing-std-forward) and drop a \u from its doc comment (-Wdocumentation-unknown-command). - ordered_map: include for std::allocator (cpplint). Ran make amalgamate. Signed-off-by: Niels Lohmann * Fix tests failing in CI on develop - unit-allocator: skip the #5640 test under MSVC STL iterator debugging, where containers allocate a debug proxy in noexcept move constructors and a failing allocation terminates; move a decrement out of an if condition (bugprone-inc-dec-in-conditions). - unit-conversions: expect the "(/0)" path with JSON_DIAGNOSTICS; compare strict enums via get<>() rather than through the noexcept operator==(ScalarType, json), which bugprone-exception-escape flags. - unit-alt-string: suppress -Wexit-time-destructors for the strict enum macro and misc-use-internal-linkage for its enum. - unit-bjdata: call the static lookup functions through the type and pass unsigned char (-Wsign-conversion on amd64). - Mark Infer false positives with @infer-ignore in unit-diagnostics, unit-pointer_access, unit-udt, and unit-conversions. - Smaller clang-tidy 22.1 findings in unit-class_parser, unit-constructor2, unit-custom-base-class, unit-locale-cpp, and unit-noexcept. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .clang-tidy | 4 ++ cmake/ci.cmake | 22 +++++-- cmake/gcc_flags.cmake | 1 - .../nlohmann/detail/conversions/to_json.hpp | 5 +- .../nlohmann/detail/input/binary_reader.hpp | 28 +++++---- .../nlohmann/detail/input/number_parse.hpp | 8 +-- include/nlohmann/detail/string_utils.hpp | 4 +- include/nlohmann/json.hpp | 13 +++- include/nlohmann/ordered_map.hpp | 3 +- single_include/nlohmann/json.hpp | 61 ++++++++++++------- tests/CMakeLists.txt | 5 +- tests/src/unit-allocator.cpp | 14 ++++- tests/src/unit-alt-string.cpp | 8 ++- tests/src/unit-bjdata.cpp | 13 ++-- tests/src/unit-class_parser.cpp | 4 +- tests/src/unit-constructor2.cpp | 2 +- tests/src/unit-conversions.cpp | 34 ++++++++--- tests/src/unit-custom-base-class.cpp | 2 +- tests/src/unit-diagnostics.cpp | 2 + tests/src/unit-locale-cpp.cpp | 1 + tests/src/unit-noexcept.cpp | 4 +- tests/src/unit-pointer_access.cpp | 4 ++ tests/src/unit-udt.cpp | 4 ++ 23 files changed, 165 insertions(+), 81 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 7132199dd..fa3e03ae3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -84,6 +84,10 @@ Checks: '*, CheckOptions: - key: hicpp-special-member-functions.AllowSoleDefaultDtor value: 1 + # clang-tidy 22.1 extended this check to classes and enums; the test files + # define many such helper types at namespace scope, which is harmless + - key: misc-use-internal-linkage.AnalyzeTypes + value: false WarningsAsErrors: '*' diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 353975b8e..c23f2679e 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -596,7 +596,12 @@ foreach(SRC_FILE ${SRC_FILES}) add_executable(single_${RELATIVE_SRC_FILE} EXCLUDE_FROM_ALL ${PROJECT_BINARY_DIR}/src_single/${RELATIVE_SRC_FILE}.cpp) target_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include) target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11) - set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") + if(RELATIVE_SRC_FILE STREQUAL "json") + # see below: report json.hpp's diagnostics without --error, so they do not fail the build + set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300) + else() + set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") + endif() # remember binary for ci_single_binaries list(APPEND single_binaries single_${RELATIVE_SRC_FILE}) # json.hpp pulls together the whole library behind heavily templated, SFINAE-based code, and @@ -658,14 +663,19 @@ function(ci_get_cmake version var) OUTPUT ${${var}} COMMAND wget -nc https://github.com/Kitware/CMake/releases/download/v${version}/cmake-${version}-linux-x86_64.tar.gz COMMAND wget -nc https://github.com/Kitware/CMake/releases/download/v${version}/cmake-${version}-SHA-256.txt - # verify the archive against Kitware's published SHA-256 sums before unpacking it - COMMAND sh -c "grep ' cmake-${version}-linux-x86_64[.]tar[.]gz$' cmake-${version}-SHA-256.txt | sha256sum -c -" - COMMAND tar xfz cmake-${version}-linux-x86_64.tar.gz - COMMAND rm cmake-${version}-linux-x86_64.tar.gz cmake-${version}-SHA-256.txt + # verify the archive against Kitware's published SHA-256 sums before unpacking it; old + # releases list the archive as "Linux-x86_64", so match case-insensitively and rewrite + # the name to the lowercase one the download was saved under + COMMAND sh -c "grep -i ' cmake-${version}-linux-x86_64[.]tar[.]gz$' cmake-${version}-SHA-256.txt | tr L l | sha256sum -c -" + # unpack into cmake-${version} directly, as the archive's top-level directory is spelled + # "Linux" in old releases and "linux" in newer ones COMMAND ${CMAKE_COMMAND} -E rm -rf cmake-${version} - COMMAND ${CMAKE_COMMAND} -E rename cmake-${version}-linux-x86_64 cmake-${version} + COMMAND ${CMAKE_COMMAND} -E make_directory cmake-${version} + COMMAND tar xfz cmake-${version}-linux-x86_64.tar.gz -C cmake-${version} --strip-components=1 + COMMAND rm cmake-${version}-linux-x86_64.tar.gz cmake-${version}-SHA-256.txt WORKING_DIRECTORY ${PROJECT_BINARY_DIR} COMMENT "Download prebuilt CMake ${version}" + VERBATIM ) else() # no prebuilt archive for this platform (e.g. macOS or Linux aarch64): build from source diff --git a/cmake/gcc_flags.cmake b/cmake/gcc_flags.cmake index 0a3272c7a..d1081ae0e 100644 --- a/cmake/gcc_flags.cmake +++ b/cmake/gcc_flags.cmake @@ -164,7 +164,6 @@ set(GCC_CXXFLAGS -Wenum-conversion -Wexceptions -Wexpansion-to-defined - -Wexperimental-fmv-target -Wexpose-global-module-tu-local -Wexternal-tu-local -Wextra diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index df5c88523..49b3c32e2 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -235,8 +235,11 @@ struct external_constructor for (auto&& x : std::forward(arr)) { j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); } + // set the parents only once all elements are in place: a push_back + // that reallocates moves the earlier elements, which does not keep + // their parent pointers + j.set_parents(); j.assert_invariant(); } #endif diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 18511071f..86f00bef6 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -614,13 +614,13 @@ class binary_reader case 0x10: // int32 { std::int32_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x12: // int64 { std::int64_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x11: // uint64 @@ -659,7 +659,7 @@ class binary_reader parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr)); } - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } /*! @@ -1905,25 +1905,25 @@ class binary_reader case 0xD0: // int 8 { std::int8_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD1: // int 16 { std::int16_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD2: // int 32 { std::int32_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD3: // int 64 { std::int64_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xDC: // array 16 @@ -2980,25 +2980,25 @@ class binary_reader case 'i': { std::int8_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'I': { std::int16_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'l': { std::int32_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'L': { std::int64_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'u': @@ -3558,7 +3558,7 @@ class binary_reader // integer -1..-10 if (byte <= 0xC1) { - return sax->number_integer(-1 - static_cast(byte - 0xB8)); + return sax->number_integer(conditional_static_cast(-1 - static_cast(byte - 0xB8))); } // 0xC2..0xF7: a UTF-8 lead byte begins a string if a continuation @@ -3877,6 +3877,8 @@ class binary_reader template bool get_to(T& dest, const input_format_t format, const char* context) { + // false positive: new_chars_read is read on the next lines + // @infer-ignore DEAD_STORE auto new_chars_read = ia.get_elements(&dest); chars_read += new_chars_read; if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) @@ -4128,6 +4130,8 @@ class binary_reader // resize() is required to make size() exactly old_size + wanted; // that is the room get_elements() is allowed to write into JSON_ASSERT(result.size() == old_size + wanted); + // false positive: bytes_read is read on the next lines + // @infer-ignore DEAD_STORE const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted); chars_read += bytes_read; if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted)) diff --git a/include/nlohmann/detail/input/number_parse.hpp b/include/nlohmann/detail/input/number_parse.hpp index b85e758cb..8971f7193 100644 --- a/include/nlohmann/detail/input/number_parse.hpp +++ b/include/nlohmann/detail/input/number_parse.hpp @@ -428,14 +428,14 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double } std::uint64_t w = 0; - int digits = 0; // significant digits in w + unsigned int digits = 0; // significant digits in w std::int64_t exponent = 0; bool truncated = false; bool in_fraction = false; for (;;) { // eight digits at a time, as long as they fit into w - while (w != 0 && digits <= 19 - 8 && last - p >= 8) + while (w != 0 && digits <= 19u - 8u && last - p >= 8) { const std::uint64_t v = read_eight_bytes(p); if (!is_eight_digits(v)) @@ -443,7 +443,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double break; } w = (w * 100000000u) + parse_eight_digits(v); - digits += 8; + digits += 8u; exponent -= in_fraction ? 8 : 0; p += 8; } @@ -459,7 +459,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double // leading zeros are not significant, but scale a fraction exponent -= in_fraction ? 1 : 0; } - else if (digits < 19) + else if (digits < 19u) { w = (w * 10u) + static_cast(c - '0'); ++digits; diff --git a/include/nlohmann/detail/string_utils.hpp b/include/nlohmann/detail/string_utils.hpp index 2495c5802..7c40f7395 100644 --- a/include/nlohmann/detail/string_utils.hpp +++ b/include/nlohmann/detail/string_utils.hpp @@ -57,7 +57,7 @@ inline std::string hex_byte(const std::uint8_t byte) Used to turn a decoded code point back into bytes: by the wide-string input adapters in input_adapters.hpp (one code point per UTF-32 unit, per UTF-16 unit outside the surrogate range, and per valid UTF-16 surrogate pair), and -by the lexer's `\uXXXX`/`\uXXXX\uYYYY` handling in lexer.hpp. Passing a +by the lexer's handling of u-escapes and surrogate pairs in lexer.hpp. Passing a code point above U+10FFFF, or one in the surrogate range U+D800..U+DFFF, is undefined behavior; callers are expected to have rejected those already (the wide-string adapters pass malformed units through unencoded instead of @@ -70,7 +70,7 @@ reaching it). @param[in] out called once for each byte of the UTF-8 encoding of @a cp */ template -void encode_utf8(std::uint32_t cp, Out&& out) +void encode_utf8(std::uint32_t cp, const Out& out) { JSON_ASSERT(cp <= 0x10FFFF); diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 91efcf27c..bbe310d9b 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6524,6 +6524,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // Any other object type places its members itself - std::map // in key order, a hash map in an order its operator== ignores - // so a member-by-member diff always reproduces target there. +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push ) +#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr +#endif if (!detail::is_ordered_map::value || (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)) { @@ -6534,6 +6538,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec common_keys = std::move(common_keys_source_order); return true; } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning( pop ) +#endif // slow path: the common keys are in a different relative // order in source and target (only possible for a @@ -7030,10 +7037,10 @@ struct formatter // NOLINT(cert-dcl58-c int indent = -1; char indent_char = ' '; - constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator + constexpr format_parse_context::iterator parse(format_parse_context& ctx) { - auto it = ctx.begin(); - const auto end = ctx.end(); + format_parse_context::iterator it = ctx.begin(); + const format_parse_context::iterator end = ctx.end(); constexpr auto is_align = [](char c) { return c == '<' || c == '>' || c == '^'; diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 7b6f63781..656f24264 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -12,12 +12,13 @@ #include // equal_to, less #include // initializer_list #include // input_iterator_tag, iterator_traits +#include // allocator #include // for operator new (placement new) #include // for out_of_range #include // forward_as_tuple #include // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible #include // forward, move, pair, piecewise_construct -#include // vector, allocator +#include // vector #include #include diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e093e4ece..73079da57 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6261,7 +6261,7 @@ inline std::string hex_byte(const std::uint8_t byte) Used to turn a decoded code point back into bytes: by the wide-string input adapters in input_adapters.hpp (one code point per UTF-32 unit, per UTF-16 unit outside the surrogate range, and per valid UTF-16 surrogate pair), and -by the lexer's `\uXXXX`/`\uXXXX\uYYYY` handling in lexer.hpp. Passing a +by the lexer's handling of u-escapes and surrogate pairs in lexer.hpp. Passing a code point above U+10FFFF, or one in the surrogate range U+D800..U+DFFF, is undefined behavior; callers are expected to have rejected those already (the wide-string adapters pass malformed units through unencoded instead of @@ -6274,7 +6274,7 @@ reaching it). @param[in] out called once for each byte of the UTF-8 encoding of @a cp */ template -void encode_utf8(std::uint32_t cp, Out&& out) +void encode_utf8(std::uint32_t cp, const Out& out) { JSON_ASSERT(cp <= 0x10FFFF); @@ -6848,8 +6848,11 @@ struct external_constructor for (auto&& x : std::forward(arr)) { j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); } + // set the parents only once all elements are in place: a push_back + // that reallocates moves the earlier elements, which does not keep + // their parent pointers + j.set_parents(); j.assert_invariant(); } #endif @@ -9464,14 +9467,14 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double } std::uint64_t w = 0; - int digits = 0; // significant digits in w + unsigned int digits = 0; // significant digits in w std::int64_t exponent = 0; bool truncated = false; bool in_fraction = false; for (;;) { // eight digits at a time, as long as they fit into w - while (w != 0 && digits <= 19 - 8 && last - p >= 8) + while (w != 0 && digits <= 19u - 8u && last - p >= 8) { const std::uint64_t v = read_eight_bytes(p); if (!is_eight_digits(v)) @@ -9479,7 +9482,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double break; } w = (w * 100000000u) + parse_eight_digits(v); - digits += 8; + digits += 8u; exponent -= in_fraction ? 8 : 0; p += 8; } @@ -9495,7 +9498,7 @@ inline bool parse_float_eisel_lemire(const char* first, const char* last, double // leading zeros are not significant, but scale a fraction exponent -= in_fraction ? 1 : 0; } - else if (digits < 19) + else if (digits < 19u) { w = (w * 10u) + static_cast(c - '0'); ++digits; @@ -14127,13 +14130,13 @@ class binary_reader case 0x10: // int32 { std::int32_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x12: // int64 { std::int64_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); + return get_number(input_format_t::bson, value) && sax->number_integer(conditional_static_cast(value)); } case 0x11: // uint64 @@ -14172,7 +14175,7 @@ class binary_reader parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr)); } - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } /*! @@ -15418,25 +15421,25 @@ class binary_reader case 0xD0: // int 8 { std::int8_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD1: // int 16 { std::int16_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD2: // int 32 { std::int32_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xD3: // int 64 { std::int64_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + return get_number(input_format_t::msgpack, number) && sax->number_integer(conditional_static_cast(number)); } case 0xDC: // array 16 @@ -16493,25 +16496,25 @@ class binary_reader case 'i': { std::int8_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'I': { std::int16_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'l': { std::int32_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'L': { std::int64_t number{}; - return get_number(input_format, number) && sax->number_integer(number); + return get_number(input_format, number) && sax->number_integer(conditional_static_cast(number)); } case 'u': @@ -17071,7 +17074,7 @@ class binary_reader // integer -1..-10 if (byte <= 0xC1) { - return sax->number_integer(-1 - static_cast(byte - 0xB8)); + return sax->number_integer(conditional_static_cast(-1 - static_cast(byte - 0xB8))); } // 0xC2..0xF7: a UTF-8 lead byte begins a string if a continuation @@ -17390,6 +17393,8 @@ class binary_reader template bool get_to(T& dest, const input_format_t format, const char* context) { + // false positive: new_chars_read is read on the next lines + // @infer-ignore DEAD_STORE auto new_chars_read = ia.get_elements(&dest); chars_read += new_chars_read; if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) @@ -17641,6 +17646,8 @@ class binary_reader // resize() is required to make size() exactly old_size + wanted; // that is the room get_elements() is allowed to write into JSON_ASSERT(result.size() == old_size + wanted); + // false positive: bytes_read is read on the next lines + // @infer-ignore DEAD_STORE const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted); chars_read += bytes_read; if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted)) @@ -26321,12 +26328,13 @@ NLOHMANN_JSON_NAMESPACE_END #include // equal_to, less #include // initializer_list #include // input_iterator_tag, iterator_traits +#include // allocator #include // for operator new (placement new) #include // for out_of_range #include // forward_as_tuple #include // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible #include // forward, move, pair, piecewise_construct -#include // vector, allocator +#include // vector // #include @@ -33213,6 +33221,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // Any other object type places its members itself - std::map // in key order, a hash map in an order its operator== ignores - // so a member-by-member diff always reproduces target there. +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push ) +#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr +#endif if (!detail::is_ordered_map::value || (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)) { @@ -33223,6 +33235,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec common_keys = std::move(common_keys_source_order); return true; } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning( pop ) +#endif // slow path: the common keys are in a different relative // order in source and target (only possible for a @@ -33719,10 +33734,10 @@ struct formatter // NOLINT(cert-dcl58-c int indent = -1; char indent_char = ' '; - constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator + constexpr format_parse_context::iterator parse(format_parse_context& ctx) { - auto it = ctx.begin(); - const auto end = ctx.end(); + format_parse_context::iterator it = ctx.begin(); + const format_parse_context::iterator end = ctx.end(); constexpr auto is_align = [](char c) { return c == '<' || c == '>' || c == '^'; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ef1300ff6..aac6fee5e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -89,7 +89,8 @@ target_compile_options(test_main PUBLIC # https://github.com/nlohmann/json/pull/3229 $<$:-diag-disable=2196> - $<$:-Wno-deprecated-declarations> + # several tests call deprecated functions on purpose to keep them covered + $<$>:-Wno-deprecated-declarations> $<$:-diag-disable=1786>) target_include_directories(test_main SYSTEM PUBLIC thirdparty/doctest) @@ -101,7 +102,7 @@ target_link_libraries(test_main PUBLIC ${NLOHMANN_JSON_TARGET_NAME}) # test-regression1 needs it on its include path (see json_test_set_test_options # below), rather than every test-* target via test_main. add_library(fifo_map_include INTERFACE) -target_include_directories(fifo_map_include INTERFACE thirdparty/fifo_map) +target_include_directories(fifo_map_include SYSTEM INTERFACE thirdparty/fifo_map) ############################################################################# # define test- and standard-specific build settings diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index f99abd2fc..fbdfaa351 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -372,6 +372,11 @@ void check_deep_copy_survives_failing_allocation(bool nest_objects) TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)") { + // With iterator debugging (MSVC STL debug builds, also used by clang-cl), + // containers allocate a debug proxy through the allocator inside their + // noexcept move constructors, so failing that allocation terminates the + // program instead of throwing std::bad_alloc. Nothing to check there. +#if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) SECTION("std::map-backed object_t") { using bad_alloc_json = nlohmann::basic_json(false); check_deep_copy_survives_failing_allocation(true); } +#endif } namespace @@ -493,9 +499,13 @@ struct countdown_allocator : std::allocator template void construct(U* p, Args&& ... args) { - if (constructions_until_failure != 0 && --constructions_until_failure == 0) + if (constructions_until_failure != 0) { - throw std::bad_alloc(); + --constructions_until_failure; + if (constructions_until_failure == 0) + { + throw std::bad_alloc(); + } } ::new (static_cast(p)) U(std::forward(args)...); diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index cac2d183d..8b86c78e8 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -16,6 +16,10 @@ #include #include +// NLOHMANN_JSON_SERIALIZE_ENUM_STRICT uses a static std::pair +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") + /* forward declarations */ class alt_string; bool operator<(const char* op1, const alt_string& op2) noexcept; // NOLINT(misc-use-internal-linkage) @@ -174,7 +178,7 @@ bool operator<(const char* op1, const alt_string& op2) noexcept return op1 < op2.str_impl; } -enum class alt_color { red, green }; +enum class alt_color { red, green }; // NOLINT(misc-use-internal-linkage) // NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(alt_color, @@ -434,3 +438,5 @@ TEST_CASE("alternative string type") CHECK_THROWS_WITH_AS(_ = doc.get(), "[json.exception.out_of_range.410] enum value out of range for alt_color: \"blue\"", alt_json::out_of_range&); } } + +DOCTEST_CLANG_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 8a0c0fc58..b6be66c9e 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -67,10 +67,9 @@ TEST_CASE("BJData") { SECTION("binary_reader BJData lookup tables") { + // both lookups are static member functions std::vector const data; - auto ia = nlohmann::detail::input_adapter(data); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - nlohmann::detail::binary_reader const br{std::move(ia), json::input_format_t::bjdata}; + using reader_t = nlohmann::detail::binary_reader; // the excluded optimized-type markers must match binary_writer's // is_bjdata_excluded_type_marker(), which encodes the same 8 markers @@ -78,13 +77,13 @@ TEST_CASE("BJData") {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z' }) { - CHECK(br.is_bjd_excluded_optimized_type(marker)); + CHECK(reader_t::is_bjd_excluded_optimized_type(static_cast(marker))); } for (const char marker : {'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x' }) { - CHECK(!br.is_bjd_excluded_optimized_type(marker)); + CHECK(!reader_t::is_bjd_excluded_optimized_type(static_cast(marker))); } // every dtype marker must round-trip to its ND-array type name @@ -96,11 +95,11 @@ TEST_CASE("BJData") }; for (const auto& type : types) { - const char* name = br.bjd_type_name(type.first); + const char* name = reader_t::bjd_type_name(static_cast(type.first)); REQUIRE(name != nullptr); CHECK(std::string(name) == type.second); } - CHECK(br.bjd_type_name('x') == nullptr); + CHECK(reader_t::bjd_type_name(static_cast('x')) == nullptr); } SECTION("individual values") diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 9444c2a35..f67ba631b 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -2001,7 +2001,7 @@ TEST_CASE("parser class") const json j = json::parse(R"({"skip": {"k1": 1, "k2": [2, {"k3": 3}]}, "keep": 1})", [&](int depth, json::parse_event_t event, json & parsed) { - static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; + static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) log.push_back(std::to_string(depth) + " " + names[static_cast(event)] + " " + parsed.dump()); if (depth == 1 && event == json::parse_event_t::object_start && first) @@ -2036,7 +2036,7 @@ TEST_CASE("parser class") // further effect") const auto record = [](std::vector& log, int depth, json::parse_event_t event, const json & parsed) { - static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; + static const char* const names[] = {"object_start", "object_end", "array_start", "array_end", "key", "value"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) log.push_back(std::to_string(depth) + " " + names[static_cast(event)] + " " + parsed.dump()); }; diff --git a/tests/src/unit-constructor2.cpp b/tests/src/unit-constructor2.cpp index 27bdcccd9..d1ff56452 100644 --- a/tests/src/unit-constructor2.cpp +++ b/tests/src/unit-constructor2.cpp @@ -85,7 +85,7 @@ TEST_CASE("other constructors and destructor") CHECK(j.type() == json::value_t::object); const json k(std::move(j)); CHECK(k.type() == json::value_t::object); - CHECK(j.type() == json::value_t::null); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) access after move is OK here + CHECK(j.type() == json::value_t::null); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) access after move is OK here } SECTION("copy assignment") diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index 99a1140cf..ff6e5e5c7 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -1571,9 +1571,15 @@ TEST_CASE("value conversion") json const j7 = {0, 1, 2, 3}; json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else CHECK_THROWS_WITH_AS((j7.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); +#endif CHECK_THROWS_WITH_AS((j8.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); @@ -1596,9 +1602,15 @@ TEST_CASE("value conversion") json const j7 = {0, 1, 2, 3}; json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else CHECK_THROWS_WITH_AS((j7.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); +#endif CHECK_THROWS_WITH_AS((j8.get>()), "[json.exception.type_error.302] type must be array, " "but is number", json::type_error&); @@ -1747,10 +1759,10 @@ TEST_CASE("Strict JSON to enum mapping") CHECK(json(strict_cards::karo) == "karo"); // json -> enum - CHECK(strict_cards::kreuz == json("kreuz")); - CHECK(strict_cards::pik == json("pik")); - CHECK(strict_cards::herz == json("herz")); - CHECK(strict_cards::karo == json("karo")); + CHECK(json("kreuz").get() == strict_cards::kreuz); + CHECK(json("pik").get() == strict_cards::pik); + CHECK(json("herz").get() == strict_cards::herz); + CHECK(json("karo").get() == strict_cards::karo); // invalid json -> exception thrown json _; @@ -1775,10 +1787,10 @@ TEST_CASE("Strict JSON to enum mapping") CHECK(json(STRICT_TS_INVALID) == json()); // json -> enum - CHECK(STRICT_TS_STOPPED == json("stopped")); - CHECK(STRICT_TS_RUNNING == json("running")); - CHECK(STRICT_TS_COMPLETED == json("completed")); - CHECK(STRICT_TS_INVALID == json()); + CHECK(json("stopped").get() == STRICT_TS_STOPPED); + CHECK(json("running").get() == STRICT_TS_RUNNING); + CHECK(json("completed").get() == STRICT_TS_COMPLETED); + CHECK(json().get() == STRICT_TS_INVALID); // invalid json -> exception thrown json _; @@ -1910,6 +1922,8 @@ TEST_CASE("std::optional") CHECK(json(opt_string) == j_string); CHECK(std::optional(j_string) == opt_string); + // false positive: Infer attributes the destruction of the temporaries above to opt_string + // @infer-ignore USE_AFTER_DELETE } SECTION("bool") @@ -1960,8 +1974,8 @@ TEST_CASE("std::optional") CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); // the conversion is noexcept exactly when converting the contained value is - static_assert(!std::is_nothrow_constructible&>::value, ""); - static_assert(std::is_nothrow_constructible&>::value, ""); + static_assert(!std::is_nothrow_constructible&>::value); + static_assert(std::is_nothrow_constructible&>::value); } #endif } diff --git a/tests/src/unit-custom-base-class.cpp b/tests/src/unit-custom-base-class.cpp index a7f466e36..138940d3d 100644 --- a/tests/src/unit-custom-base-class.cpp +++ b/tests/src/unit-custom-base-class.cpp @@ -234,7 +234,7 @@ TEST_CASE("JSON Node Metadata") // travel with it, just as it does for copy, move, and assignment using json = json_with_metadata; std::vector values; - for (int v : + for (const int v : { 5, 3, 9, 1, 7, 2, 8, 4, 6, 0, 15, 13, 19, 11, 17, 12, 18, 14, 16, 10, 25, 23, 29, 21, 27, 22, 28, 24, 26, 20, 35, 33 diff --git a/tests/src/unit-diagnostics.cpp b/tests/src/unit-diagnostics.cpp index 62385551c..1ada023b7 100644 --- a/tests/src/unit-diagnostics.cpp +++ b/tests/src/unit-diagnostics.cpp @@ -77,6 +77,8 @@ TEST_CASE("Better diagnostics") SECTION("Parse error") { json _; + // false positive: a default-constructed json is a valid null value + // @infer-ignore NULLPTR_DEREFERENCE CHECK_THROWS_WITH_AS(_ = json::parse(""), "[json.exception.parse_error.101] parse error at line 1, column 1: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error); } diff --git a/tests/src/unit-locale-cpp.cpp b/tests/src/unit-locale-cpp.cpp index 3b9593a20..626296825 100644 --- a/tests/src/unit-locale-cpp.cpp +++ b/tests/src/unit-locale-cpp.cpp @@ -406,6 +406,7 @@ struct LocaleSwitchingStreambuf final : std::streambuf std::string locale_after_first_write; bool switched = false; + protected: std::streamsize xsputn(const char* s, std::streamsize n) override { if (!switched) diff --git a/tests/src/unit-noexcept.cpp b/tests/src/unit-noexcept.cpp index 5e330c0a1..637915f24 100644 --- a/tests/src/unit-noexcept.cpp +++ b/tests/src/unit-noexcept.cpp @@ -60,9 +60,9 @@ TEST_CASE("noexcept") { // silence -Wunneeded-internal-declaration errors static_cast(static_cast(&to_json)); - static_cast(static_cast(&to_json)); + static_cast(static_cast(&to_json)); // NOLINT(readability-redundant-casting): selects the overload static_cast(static_cast(&from_json)); - static_cast(static_cast(&from_json)); + static_cast(static_cast(&from_json)); // NOLINT(readability-redundant-casting): selects the overload SECTION("nothrow-copy-constructible exceptions") { diff --git a/tests/src/unit-pointer_access.cpp b/tests/src/unit-pointer_access.cpp index c1e341917..33a4e2047 100644 --- a/tests/src/unit-pointer_access.cpp +++ b/tests/src/unit-pointer_access.cpp @@ -79,6 +79,8 @@ TEST_CASE("pointer access") // check if pointers are returned correctly const test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // false positive: p1 is non-null, as value has type test_type + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -108,6 +110,8 @@ TEST_CASE("pointer access") // check if pointers are returned correctly test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // false positive: p1 is non-null, as value has type test_type + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); diff --git a/tests/src/unit-udt.cpp b/tests/src/unit-udt.cpp index 24f763f96..5ad72b208 100644 --- a/tests/src/unit-udt.cpp +++ b/tests/src/unit-udt.cpp @@ -446,6 +446,8 @@ TEST_CASE("adl_serializer specialization" * doctest::test_suite("udt")) auto optPerson = j.get>(); REQUIRE(optPerson); + // false positive: REQUIRE above guarantees optPerson is non-null + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*optPerson == person); j = nullptr; @@ -559,6 +561,8 @@ TEST_CASE("Non-copyable types" * doctest::test_suite("udt")) auto optPerson = j.get>(); REQUIRE(optPerson); + // false positive: REQUIRE above guarantees optPerson is non-null + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*optPerson == person); j = nullptr; From 48ff79647fec5b3cc92bc535c3383ab4e30ee95a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:24:33 +0200 Subject: [PATCH 04/27] Bump the codeql-action group with 4 updates (#5743) Bumps the codeql-action group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.38.1 to 4.38.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/1c5b675653bb5c22dbe9b12b556ec555138e09fd...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2) Updates `github/codeql-action/autobuild` from 4.38.1 to 4.38.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/1c5b675653bb5c22dbe9b12b556ec555138e09fd...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2) Updates `github/codeql-action/analyze` from 4.38.1 to 4.38.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/1c5b675653bb5c22dbe9b12b556ec555138e09fd...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2) Updates `github/codeql-action/upload-sarif` from 4.38.1 to 4.38.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/1c5b675653bb5c22dbe9b12b556ec555138e09fd...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.38.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action - dependency-name: github/codeql-action/autobuild dependency-version: 4.38.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action - dependency-name: github/codeql-action/analyze dependency-version: 4.38.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.38.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/flawfinder.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/semgrep.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 74ecdfa91..9e86353c6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,14 +38,14 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: c-cpp # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 diff --git a/.github/workflows/flawfinder.yml b/.github/workflows/flawfinder.yml index 9f3b5562a..f37539a73 100644 --- a/.github/workflows/flawfinder.yml +++ b/.github/workflows/flawfinder.yml @@ -47,6 +47,6 @@ jobs: output: 'flawfinder_results.sarif' - name: Upload analysis results to GitHub Security tab - uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: sarif_file: ${{github.workspace}}/flawfinder_results.sarif diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 5e4b0c315..bbf11dd28 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -80,6 +80,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: sarif_file: results.sarif diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 434e1f3bb..d8c717e83 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -65,7 +65,7 @@ jobs: # Upload SARIF file generated in previous step - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: sarif_file: semgrep.sarif if: always() From 63c10a51fc63a478f6563f9fab755a4b1d5c29b7 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 2 Oct 2026 11:32:15 +0200 Subject: [PATCH 05/27] Review and extend the documentation, and check it in CI (#5638) * Review and extend the documentation, and check it in CI A review of all documentation pages found factual errors, dead links, missing cross-references, and gaps in examples. This fixes them and adds checks so the same problems are caught automatically. Fixes: - wrong signatures and version histories (operator!= C++20 member, binary() subtype type, get(), JSON_NO_THREAD_LOCAL, ...) - stale descriptions (number parsing since #5283, UBJSON table, SAX example that no longer compiled, tsl::ordered_map advice) - dead internal and external links; repology.org badges (the domain is suspended) replaced by badges that query the registries directly - deprecation notes link the migration guide; the guide itself fixed Additions: - "See also" sections, cross-references, 25 runnable examples, 12 Mermaid diagrams, new API pages for json_pointer::operator<=> and byte_container_with_subtype::operator==/!= - landing page, guides for untrusted input and performance - "unreleased" badge after versions newer than the latest release Checks: - strict documentation build (broken links/anchors fail it); CI and the publish workflow fetch the full history the build needs - weekly external link check, Mermaid syntax check in CI - check_structure.py: example titles, heading levels, alt texts, header links, docset index coverage; its unused-example check works again - all examples produce the same output on every platform Signed-off-by: Niels Lohmann * Keep the customer links that could not be fixed A dead link on the customers page is still the evidence of where the use of the library was documented. Keep the original URLs of the entries without a working replacement (Marne, Cisco Webex Desk Camera, Philips Hue, CyberArk) and exclude exactly these URLs from the link check. Signed-off-by: Niels Lohmann * Correct the duplicate-key recipe's claim about SAX positions The SAX interface's key() receives no position either; only parse_error() does. Also note that the recipe does not report the path to the repeated key (see discussion #5085). Signed-off-by: Niels Lohmann * Say the library is available as a single header and mention json_fwd.hpp Signed-off-by: Niels Lohmann * Correct documentation errors found while hunting for bugs - patch/patch_inplace: list the JSON pointer errors parse_error.106-109 and out_of_range.402/404, and quote the actual parse_error.105 message. - unflatten: list parse_error.106/107/108 and out_of_range.404. - to_bson: list out_of_range.415 (binary subtype above 255) and note that 412 and 415 are new in 3.13.0. - to_string: state that string_t must be convertible to std::string, also in the StringType requirements table. - JSON Lines: a `while (input >> j)` loop also throws after the last value for concatenated JSON values; show a loop that works for both. - BON8: a string gets 0xFF only if nothing follows it in the message; a string at the end of an array or object is ended by 0xFE. - custom_string_type.hpp: add operator+=(char), which the "Always required" list asks for (json_pointer::to_string, flatten, unflatten, and diff did not compile), and an ADL int_to_string for diff and items. Signed-off-by: Niels Lohmann * Cache the release headers with functools.lru_cache Codacy (Pylint) flagged the mutable default argument that header() used as its cache. functools.lru_cache keeps the same memoization without it. The script's output is unchanged. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .github/CONTRIBUTING.md | 10 + .github/dependabot.yml | 11 + .github/workflows/check_docs_links.yml | 46 + .github/workflows/publish_documentation.yml | 3 + .github/workflows/ubuntu.yml | 8 +- .gitignore | 2 + README.md | 1 - cmake/ci.cmake | 6 + docs/Makefile | 5 +- docs/docset/docSet.sql | 36 + docs/mkdocs/Makefile | 13 +- docs/mkdocs/docs/api/basic_json/accept.md | 19 +- docs/mkdocs/docs/api/basic_json/array_t.md | 13 +- docs/mkdocs/docs/api/basic_json/at.md | 14 + docs/mkdocs/docs/api/basic_json/basic_json.md | 48 +- docs/mkdocs/docs/api/basic_json/begin.md | 8 + docs/mkdocs/docs/api/basic_json/binary.md | 13 +- docs/mkdocs/docs/api/basic_json/binary_t.md | 10 +- docs/mkdocs/docs/api/basic_json/boolean_t.md | 8 +- docs/mkdocs/docs/api/basic_json/cbegin.md | 7 + .../docs/api/basic_json/cbor_tag_handler_t.md | 6 + docs/mkdocs/docs/api/basic_json/cend.md | 7 + docs/mkdocs/docs/api/basic_json/clear.md | 5 + docs/mkdocs/docs/api/basic_json/contains.md | 12 + docs/mkdocs/docs/api/basic_json/crbegin.md | 7 + docs/mkdocs/docs/api/basic_json/crend.md | 7 + .../basic_json/default_object_comparator_t.md | 5 + docs/mkdocs/docs/api/basic_json/emplace.md | 3 +- .../docs/api/basic_json/emplace_back.md | 5 + docs/mkdocs/docs/api/basic_json/empty.md | 5 + docs/mkdocs/docs/api/basic_json/end.md | 7 + .../docs/api/basic_json/error_handler_t.md | 5 + docs/mkdocs/docs/api/basic_json/flatten.md | 17 +- docs/mkdocs/docs/api/basic_json/from_bson.md | 2 + docs/mkdocs/docs/api/basic_json/from_cbor.md | 2 + .../docs/api/basic_json/from_msgpack.md | 2 + .../mkdocs/docs/api/basic_json/from_ubjson.md | 2 + docs/mkdocs/docs/api/basic_json/get.md | 32 +- .../docs/api/basic_json/get_allocator.md | 13 + docs/mkdocs/docs/api/basic_json/get_to.md | 9 +- .../docs/api/basic_json/input_format_t.md | 5 + docs/mkdocs/docs/api/basic_json/insert.md | 12 +- docs/mkdocs/docs/api/basic_json/is_array.md | 7 + docs/mkdocs/docs/api/basic_json/is_binary.md | 6 + docs/mkdocs/docs/api/basic_json/is_boolean.md | 5 + .../docs/api/basic_json/is_discarded.md | 18 +- docs/mkdocs/docs/api/basic_json/is_null.md | 7 + docs/mkdocs/docs/api/basic_json/is_object.md | 7 + docs/mkdocs/docs/api/basic_json/is_string.md | 6 + docs/mkdocs/docs/api/basic_json/items.md | 2 + .../docs/api/basic_json/json_base_class_t.md | 8 +- .../docs/api/basic_json/json_serializer.md | 11 +- docs/mkdocs/docs/api/basic_json/max_size.md | 6 + .../mkdocs/docs/api/basic_json/merge_patch.md | 4 + .../docs/api/basic_json/number_float_t.md | 15 +- .../docs/api/basic_json/number_integer_t.md | 15 +- .../docs/api/basic_json/number_unsigned_t.md | 15 +- .../api/basic_json/object_comparator_t.md | 5 + docs/mkdocs/docs/api/basic_json/object_t.md | 21 +- docs/mkdocs/docs/api/basic_json/operator+=.md | 6 + docs/mkdocs/docs/api/basic_json/operator[].md | 12 + .../docs/api/basic_json/operator_ValueType.md | 9 +- .../mkdocs/docs/api/basic_json/operator_eq.md | 4 +- .../mkdocs/docs/api/basic_json/operator_ge.md | 10 + .../mkdocs/docs/api/basic_json/operator_le.md | 10 + .../mkdocs/docs/api/basic_json/operator_ne.md | 33 +- .../docs/api/basic_json/operator_value_t.md | 5 + docs/mkdocs/docs/api/basic_json/parse.md | 20 +- .../docs/api/basic_json/parse_event_t.md | 15 + .../docs/api/basic_json/parser_callback_t.md | 4 +- docs/mkdocs/docs/api/basic_json/patch.md | 33 +- .../docs/api/basic_json/patch_inplace.md | 34 +- docs/mkdocs/docs/api/basic_json/push_back.md | 6 + docs/mkdocs/docs/api/basic_json/rbegin.md | 7 + docs/mkdocs/docs/api/basic_json/rend.md | 7 + docs/mkdocs/docs/api/basic_json/sax_parse.md | 2 + docs/mkdocs/docs/api/basic_json/size.md | 5 + docs/mkdocs/docs/api/basic_json/std_hash.md | 4 + docs/mkdocs/docs/api/basic_json/string_t.md | 18 +- docs/mkdocs/docs/api/basic_json/swap.md | 20 +- docs/mkdocs/docs/api/basic_json/to_bjdata.md | 19 +- docs/mkdocs/docs/api/basic_json/to_bon8.md | 17 +- docs/mkdocs/docs/api/basic_json/to_bson.md | 18 +- docs/mkdocs/docs/api/basic_json/to_msgpack.md | 17 +- docs/mkdocs/docs/api/basic_json/to_string.md | 3 +- docs/mkdocs/docs/api/basic_json/to_ubjson.md | 19 +- docs/mkdocs/docs/api/basic_json/type.md | 6 + docs/mkdocs/docs/api/basic_json/type_name.md | 5 + docs/mkdocs/docs/api/basic_json/unflatten.md | 9 + docs/mkdocs/docs/api/basic_json/update.md | 6 +- docs/mkdocs/docs/api/basic_json/value.md | 27 + docs/mkdocs/docs/api/basic_json/value_t.md | 18 + .../mkdocs/docs/api/basic_json/~basic_json.md | 5 + .../byte_container_with_subtype.md | 8 + .../clear_subtype.md | 6 + .../has_subtype.md | 6 + .../api/byte_container_with_subtype/index.md | 4 +- .../operator_eq.md | 48 + .../operator_ne.md | 47 + .../set_subtype.md | 6 + .../byte_container_with_subtype/subtype.md | 6 + docs/mkdocs/docs/api/json.md | 5 + docs/mkdocs/docs/api/json_pointer/back.md | 11 + docs/mkdocs/docs/api/json_pointer/empty.md | 6 + docs/mkdocs/docs/api/json_pointer/front.md | 10 + docs/mkdocs/docs/api/json_pointer/index.md | 22 +- .../docs/api/json_pointer/json_pointer.md | 13 + .../docs/api/json_pointer/operator_eq.md | 7 + .../docs/api/json_pointer/operator_ne.md | 7 + .../docs/api/json_pointer/operator_slash.md | 10 + .../docs/api/json_pointer/operator_slasheq.md | 12 + .../api/json_pointer/operator_spaceship.md | 74 + .../api/json_pointer/operator_string_t.md | 5 +- docs/mkdocs/docs/api/json_pointer/pop_back.md | 10 + .../mkdocs/docs/api/json_pointer/pop_front.md | 9 + .../mkdocs/docs/api/json_pointer/push_back.md | 11 + .../docs/api/json_pointer/push_front.md | 10 + docs/mkdocs/docs/api/json_pointer/string_t.md | 5 + .../mkdocs/docs/api/json_pointer/to_string.md | 13 + docs/mkdocs/docs/api/json_sax/binary.md | 6 + docs/mkdocs/docs/api/json_sax/boolean.md | 5 + docs/mkdocs/docs/api/json_sax/end_array.md | 5 + docs/mkdocs/docs/api/json_sax/end_object.md | 6 + docs/mkdocs/docs/api/json_sax/index.md | 21 + docs/mkdocs/docs/api/json_sax/key.md | 6 + docs/mkdocs/docs/api/json_sax/null.md | 5 + docs/mkdocs/docs/api/json_sax/number_float.md | 6 + .../docs/api/json_sax/number_integer.md | 6 + .../docs/api/json_sax/number_unsigned.md | 6 + docs/mkdocs/docs/api/json_sax/parse_error.md | 6 + docs/mkdocs/docs/api/json_sax/start_array.md | 5 + docs/mkdocs/docs/api/json_sax/start_object.md | 6 + docs/mkdocs/docs/api/json_sax/string.md | 5 + docs/mkdocs/docs/api/macros/index.md | 1 + docs/mkdocs/docs/api/macros/json_assert.md | 4 +- .../macros/json_brace_init_copy_semantics.md | 4 +- .../api/macros/json_diagnostic_positions.md | 4 +- .../docs/api/macros/json_diagnostics.md | 6 +- .../macros/json_disable_enum_serialization.md | 6 +- ...json_disable_tuple_reference_conversion.md | 4 +- .../mkdocs/docs/api/macros/json_has_cpp_11.md | 7 + .../docs/api/macros/json_has_filesystem.md | 5 + .../mkdocs/docs/api/macros/json_has_ranges.md | 7 + .../docs/api/macros/json_has_static_rtti.md | 7 +- .../docs/api/macros/json_has_std_format.md | 4 + .../macros/json_has_three_way_comparison.md | 5 + docs/mkdocs/docs/api/macros/json_no_io.md | 5 + .../docs/api/macros/json_no_thread_local.md | 6 +- .../macros/json_precise_stream_position.md | 4 +- .../macros/json_skip_library_version_check.md | 11 +- .../json_skip_unsupported_compiler_check.md | 7 +- .../api/macros/json_strict_nul_handling.md | 5 +- .../docs/api/macros/json_use_global_udls.md | 6 +- .../macros/json_use_implicit_conversions.md | 2 + ...n_use_legacy_discarded_value_comparison.md | 2 + .../docs/api/macros/json_use_simdutf.md | 12 +- .../macros/nlohmann_define_derived_type.md | 2 +- .../macros/nlohmann_define_type_intrusive.md | 6 +- .../nlohmann_define_type_non_intrusive.md | 6 +- .../macros/nlohmann_define_type_with_names.md | 19 +- .../macros/nlohmann_json_serialize_enum.md | 4 +- .../nlohmann_json_serialize_enum_strict.md | 6 +- docs/mkdocs/docs/api/operator_gtgt.md | 2 + docs/mkdocs/docs/api/operator_literal_json.md | 7 +- .../docs/api/operator_literal_json_pointer.md | 7 +- docs/mkdocs/docs/api/operator_ltlt.md | 2 + docs/mkdocs/docs/api/ordered_map.md | 4 +- docs/mkdocs/docs/community/assurance_case.md | 11 + docs/mkdocs/docs/css/custom.css | 16 + .../docs/examples/accept__iterator_pair.cpp | 15 + .../examples/accept__iterator_pair.output | 1 + .../examples/basic_json__BasicJsonType.cpp | 22 + .../examples/basic_json__BasicJsonType.output | 3 + .../examples/basic_json__CompatibleType.cpp | 6 +- .../basic_json__CompatibleType.output | 4 +- ...ontainer_with_subtype__operator__equal.cpp | 23 + ...ainer_with_subtype__operator__equal.output | 4 + ...ainer_with_subtype__operator__notequal.cpp | 23 + ...er_with_subtype__operator__notequal.output | 4 + .../docs/examples/custom_string_type.hpp | 20 +- docs/mkdocs/docs/examples/flatten__empty.cpp | 23 + .../docs/examples/flatten__empty.output | 11 + .../docs/examples/get__BasicJsonType.cpp | 17 + .../docs/examples/get__BasicJsonType.output | 2 + .../docs/examples/get__ValueType_const.cpp | 4 +- .../docs/examples/get__ValueType_const.output | 8 +- docs/mkdocs/docs/examples/get_to.cpp | 4 +- docs/mkdocs/docs/examples/get_to.output | 8 +- .../docs/examples/is_discarded__parse.cpp | 22 + .../docs/examples/is_discarded__parse.output | 3 + ...json_pointer__operator_spaceship.c++20.cpp | 32 + ...n_pointer__operator_spaceship.c++20.output | 3 + .../docs/examples/operator__ValueType.cpp | 4 +- .../docs/examples/operator__ValueType.output | 8 +- docs/mkdocs/docs/examples/parse_event_t.cpp | 44 + .../mkdocs/docs/examples/parse_event_t.output | 10 + .../mkdocs/docs/examples/patch__exception.cpp | 36 + .../docs/examples/patch__exception.output | 6 + .../examples/patch_inplace__exception.cpp | 38 + .../examples/patch_inplace__exception.output | 5 + .../mkdocs/docs/examples/sax_no_exception.cpp | 40 + .../docs/examples/sax_no_exception.output | 5 + .../docs/examples/to_bjdata__exception.cpp | 20 + .../docs/examples/to_bjdata__exception.output | 1 + .../docs/examples/to_bon8__exception.cpp | 22 + .../docs/examples/to_bon8__exception.output | 1 + .../docs/examples/to_bson__exception.cpp | 23 + .../docs/examples/to_bson__exception.output | 1 + .../docs/examples/to_msgpack__exception.cpp | 20 + .../examples/to_msgpack__exception.output | 1 + .../docs/examples/to_ubjson__exception.cpp | 20 + .../docs/examples/to_ubjson__exception.output | 1 + .../mkdocs/docs/examples/value__exception.cpp | 33 + .../docs/examples/value__exception.output | 2 + docs/mkdocs/docs/features/arbitrary_types.md | 100 +- docs/mkdocs/docs/features/assertions.md | 8 +- .../docs/features/binary_formats/bjdata.md | 6 +- .../docs/features/binary_formats/bon8.md | 11 +- .../docs/features/binary_formats/bson.md | 4 +- .../docs/features/binary_formats/cbor.md | 4 +- .../features/binary_formats/messagepack.md | 4 +- .../docs/features/binary_formats/ubjson.md | 52 +- docs/mkdocs/docs/features/binary_values.md | 28 +- docs/mkdocs/docs/features/comments.md | 2 +- .../features/element_access/checked_access.md | 6 +- .../features/element_access/default_value.md | 6 +- .../docs/features/element_access/index.md | 18 +- .../element_access/unchecked_access.md | 15 +- docs/mkdocs/docs/features/enum_conversion.md | 37 +- docs/mkdocs/docs/features/index.md | 6 +- docs/mkdocs/docs/features/iterators.md | 20 +- docs/mkdocs/docs/features/json_patch.md | 33 +- docs/mkdocs/docs/features/json_pointer.md | 3 +- docs/mkdocs/docs/features/macros.md | 3 +- docs/mkdocs/docs/features/merge_patch.md | 14 +- docs/mkdocs/docs/features/object_order.md | 4 +- docs/mkdocs/docs/features/parsing/index.md | 11 + .../docs/features/parsing/json_lines.md | 20 +- .../docs/features/parsing/parse_exceptions.md | 64 +- .../docs/features/parsing/parser_callbacks.md | 23 +- .../docs/features/parsing/untrusted_input.md | 163 ++ docs/mkdocs/docs/features/performance.md | 215 +++ docs/mkdocs/docs/features/serialization.md | 4 +- .../docs/features/types/number_handling.md | 21 +- .../features/types/template_parameters.md | 27 +- docs/mkdocs/docs/home/customers.md | 14 +- docs/mkdocs/docs/home/exceptions.md | 46 +- docs/mkdocs/docs/home/faq.md | 30 +- docs/mkdocs/docs/home/license.md | 2 +- docs/mkdocs/docs/home/releases.md | 5 + docs/mkdocs/docs/home/sponsors.md | 2 +- docs/mkdocs/docs/index.md | 113 +- .../docs/integration/bazel/MODULE.bazel | 2 +- docs/mkdocs/docs/integration/cmake.md | 25 +- docs/mkdocs/docs/integration/index.md | 32 +- .../docs/integration/migration_guide.md | 31 +- .../mkdocs/docs/integration/msys2/example.cpp | 10 + .../nuget/nuget-package-content.png | Bin 19422 -> 0 bytes .../nuget/nuget-project-changes.png | Bin 30826 -> 0 bytes .../nuget/nuget-project-makefile.png | Bin 55236 -> 0 bytes .../docs/integration/package_managers.md | 275 ++- docs/mkdocs/docs/integration/pkg-config.md | 3 + .../docs/integration/swift/Package.swift | 17 + .../mkdocs/docs/integration/swift/example.cpp | 10 + docs/mkdocs/hooks/unreleased_versions.py | 82 + docs/mkdocs/mkdocs.yml | 43 +- docs/mkdocs/scripts/check_structure.py | 114 +- docs/mkdocs/scripts/check_version_history.py | 101 + docs/mkdocs/scripts/mermaid/check_mermaid.mjs | 54 + docs/mkdocs/scripts/mermaid/package-lock.json | 1619 +++++++++++++++++ docs/mkdocs/scripts/mermaid/package.json | 10 + include/nlohmann/detail/input/parser.hpp | 5 +- include/nlohmann/detail/json_pointer.hpp | 7 +- include/nlohmann/detail/macro_scope.hpp | 2 +- include/nlohmann/json.hpp | 8 +- single_include/nlohmann/json.hpp | 22 +- 276 files changed, 5318 insertions(+), 624 deletions(-) create mode 100644 .github/workflows/check_docs_links.yml create mode 100644 docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md create mode 100644 docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md create mode 100644 docs/mkdocs/docs/api/json_pointer/operator_spaceship.md create mode 100644 docs/mkdocs/docs/examples/accept__iterator_pair.cpp create mode 100644 docs/mkdocs/docs/examples/accept__iterator_pair.output create mode 100644 docs/mkdocs/docs/examples/basic_json__BasicJsonType.cpp create mode 100644 docs/mkdocs/docs/examples/basic_json__BasicJsonType.output create mode 100644 docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.cpp create mode 100644 docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.output create mode 100644 docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.cpp create mode 100644 docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.output create mode 100644 docs/mkdocs/docs/examples/flatten__empty.cpp create mode 100644 docs/mkdocs/docs/examples/flatten__empty.output create mode 100644 docs/mkdocs/docs/examples/get__BasicJsonType.cpp create mode 100644 docs/mkdocs/docs/examples/get__BasicJsonType.output create mode 100644 docs/mkdocs/docs/examples/is_discarded__parse.cpp create mode 100644 docs/mkdocs/docs/examples/is_discarded__parse.output create mode 100644 docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.cpp create mode 100644 docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.output create mode 100644 docs/mkdocs/docs/examples/parse_event_t.cpp create mode 100644 docs/mkdocs/docs/examples/parse_event_t.output create mode 100644 docs/mkdocs/docs/examples/patch__exception.cpp create mode 100644 docs/mkdocs/docs/examples/patch__exception.output create mode 100644 docs/mkdocs/docs/examples/patch_inplace__exception.cpp create mode 100644 docs/mkdocs/docs/examples/patch_inplace__exception.output create mode 100644 docs/mkdocs/docs/examples/sax_no_exception.cpp create mode 100644 docs/mkdocs/docs/examples/sax_no_exception.output create mode 100644 docs/mkdocs/docs/examples/to_bjdata__exception.cpp create mode 100644 docs/mkdocs/docs/examples/to_bjdata__exception.output create mode 100644 docs/mkdocs/docs/examples/to_bon8__exception.cpp create mode 100644 docs/mkdocs/docs/examples/to_bon8__exception.output create mode 100644 docs/mkdocs/docs/examples/to_bson__exception.cpp create mode 100644 docs/mkdocs/docs/examples/to_bson__exception.output create mode 100644 docs/mkdocs/docs/examples/to_msgpack__exception.cpp create mode 100644 docs/mkdocs/docs/examples/to_msgpack__exception.output create mode 100644 docs/mkdocs/docs/examples/to_ubjson__exception.cpp create mode 100644 docs/mkdocs/docs/examples/to_ubjson__exception.output create mode 100644 docs/mkdocs/docs/examples/value__exception.cpp create mode 100644 docs/mkdocs/docs/examples/value__exception.output create mode 100644 docs/mkdocs/docs/features/parsing/untrusted_input.md create mode 100644 docs/mkdocs/docs/features/performance.md create mode 100644 docs/mkdocs/docs/integration/msys2/example.cpp delete mode 100644 docs/mkdocs/docs/integration/nuget/nuget-package-content.png delete mode 100644 docs/mkdocs/docs/integration/nuget/nuget-project-changes.png delete mode 100644 docs/mkdocs/docs/integration/nuget/nuget-project-makefile.png create mode 100644 docs/mkdocs/docs/integration/swift/Package.swift create mode 100644 docs/mkdocs/docs/integration/swift/example.cpp create mode 100644 docs/mkdocs/hooks/unreleased_versions.py create mode 100644 docs/mkdocs/scripts/check_version_history.py create mode 100644 docs/mkdocs/scripts/mermaid/check_mermaid.mjs create mode 100644 docs/mkdocs/scripts/mermaid/package-lock.json create mode 100644 docs/mkdocs/scripts/mermaid/package.json diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4125b066a..6f3d0bef3 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -142,6 +142,16 @@ The documentation will then be available at . See the do [mkdocs](https://www.mkdocs.org) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) for more information. +Before opening a pull request, check the documentation like the CI does: + +```shell +make build -C docs/mkdocs # strict build: fails on broken links, anchors, and structure problems +make check_mermaid -C docs/mkdocs # checks the Mermaid diagrams (requires Node.js) +``` + +A new API page also needs an entry in [`docs/docset/docSet.sql`](https://github.com/nlohmann/json/blob/develop/docs/docset/docSet.sql), +the search index of the docset; `make build` reports missing entries. + ### Amalgamate the source code The single-header files diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4bf8f8782..5336d0df5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,17 @@ updates: cooldown: default-days: 7 + - package-ecosystem: npm + directory: /docs/mkdocs/scripts/mermaid + schedule: + interval: daily + cooldown: + default-days: 7 + ignore: + # Material for MkDocs loads mermaid@11; keep the checker on the same major version + - dependency-name: mermaid + update-types: ["version-update:semver-major"] + - package-ecosystem: pip directory: /tools/astyle schedule: diff --git a/.github/workflows/check_docs_links.yml b/.github/workflows/check_docs_links.yml new file mode 100644 index 000000000..342c96bff --- /dev/null +++ b/.github/workflows/check_docs_links.yml @@ -0,0 +1,46 @@ +name: Check documentation links + +# check the links of the documentation weekly; external links break without any change in this repository +on: + schedule: + - cron: '17 4 * * 1' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + check_docs_links: + if: github.repository == 'nlohmann/json' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Harden Runner + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 + with: + egress-policy: audit + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install virtual environment + run: make install_venv -C docs/mkdocs + + - name: Check links + shell: bash # adds -o pipefail, so tee does not hide the exit code + run: make link_check -C docs/mkdocs 2>&1 | tee link_check.log + + - name: Summarize broken links + if: failure() + run: | + { + echo '### Broken documentation links' + echo '```' + grep 'invalid url' link_check.log || true + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/publish_documentation.yml b/.github/workflows/publish_documentation.yml index f65d63a92..5e39574bc 100644 --- a/.github/workflows/publish_documentation.yml +++ b/.github/workflows/publish_documentation.yml @@ -43,6 +43,9 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + # the git-revision-date-localized plugin needs the history for correct "last update" dates and so the + # strict build does not fail on a shallow clone + fetch-depth: 0 - name: Install virtual environment run: make install_venv -C docs/mkdocs diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 4612dcaa6..f5addb92e 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -400,7 +400,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: [ci_test_examples, ci_test_build_documentation] + target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid] steps: - name: Harden Runner uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 @@ -410,6 +410,12 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + # the git-revision-date-localized plugin needs the history; a shallow clone makes the strict build fail + fetch-depth: ${{ matrix.target == 'ci_test_build_documentation' && '0' || '1' }} + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + if: matrix.target == 'ci_test_documentation_mermaid' + with: + node-version: 24 - name: Run CMake run: cmake -S . -B build -DJSON_CI=On - name: Build diff --git a/.gitignore b/.gitignore index 03fe8147e..474b62347 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ /docs/docset/docSet.dsidx /docs/mkdocs/.cache/ /docs/mkdocs/docs/__pycache__/ +/docs/mkdocs/hooks/__pycache__/ +/docs/mkdocs/scripts/mermaid/node_modules/ /docs/mkdocs/site/ /docs/mkdocs/venv/ diff --git a/README.md b/README.md index 19b238364..ae0ad12d2 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ [![Documentation](https://img.shields.io/badge/docs-mkdocs-blue.svg)](https://json.nlohmann.me) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/develop/LICENSE.MIT) [![GitHub Releases](https://img.shields.io/github/release/nlohmann/json.svg)](https://github.com/nlohmann/json/releases) -[![Packaging status](https://repology.org/badge/tiny-repos/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) [![GitHub Downloads](https://img.shields.io/github/downloads/nlohmann/json/total)](https://github.com/nlohmann/json/releases) [![GitHub Issues](https://img.shields.io/github/issues/nlohmann/json.svg)](https://github.com/nlohmann/json/issues) [![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/nlohmann/json.svg)](https://isitmaintained.com/project/nlohmann/json "Average time to resolve an issue") diff --git a/cmake/ci.cmake b/cmake/ci.cmake index c23f2679e..e67aba6d3 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -901,6 +901,12 @@ add_custom_target(ci_test_build_documentation COMMENT "Build the documentation" ) +add_custom_target(ci_test_documentation_mermaid + COMMAND make check_mermaid + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs/mkdocs + COMMENT "Check the Mermaid diagrams of the documentation" +) + ############################################################################### # Clean up all generated files. ############################################################################### diff --git a/docs/Makefile b/docs/Makefile index a61c30182..0df97862a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -46,9 +46,10 @@ create_output: $(EXAMPLES:.cpp=.output) # check output of all stand-alone example files check_output: $(EXAMPLES:.cpp=.test) -# check output of all stand-alone example files (exclude files with platform-dependent output.) +# check output of all stand-alone example files (exclude files whose output depends on the platform by nature: +# library and compiler information, container size limits, and hash values) # This target is used in the CI (ci_test_documentation). -check_output_portable: $(filter-out mkdocs/docs/examples/meta.test mkdocs/docs/examples/max_size.test mkdocs/docs/examples/std_hash.test mkdocs/docs/examples/basic_json__CompatibleType.test,$(EXAMPLES:.cpp=.test)) +check_output_portable: $(filter-out mkdocs/docs/examples/meta.test mkdocs/docs/examples/max_size.test mkdocs/docs/examples/std_hash.test,$(EXAMPLES:.cpp=.test)) clean: rm -fr $(EXAMPLES:.cpp=) diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 6ec90dcfc..477b2f9fe 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -10,9 +10,12 @@ INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype', INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::byte_container_with_subtype', 'Constructor', 'api/byte_container_with_subtype/byte_container_with_subtype/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::clear_subtype', 'Method', 'api/byte_container_with_subtype/clear_subtype/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::has_subtype', 'Method', 'api/byte_container_with_subtype/has_subtype/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::operator!=', 'Operator', 'api/byte_container_with_subtype/operator_ne/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::operator==', 'Operator', 'api/byte_container_with_subtype/operator_eq/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::set_subtype', 'Method', 'api/byte_container_with_subtype/set_subtype/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('byte_container_with_subtype::subtype', 'Method', 'api/byte_container_with_subtype/subtype/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json', 'Class', 'api/basic_json/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('format_as', 'Function', 'api/basic_json/format_as/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html'); @@ -132,15 +135,19 @@ INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/ind INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer', 'Class', 'api/json_pointer/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::back', 'Method', 'api/json_pointer/back/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::empty', 'Method', 'api/json_pointer/empty/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::front', 'Method', 'api/json_pointer/front/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::json_pointer', 'Constructor', 'api/json_pointer/json_pointer/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator==', 'Operator', 'api/json_pointer/operator_eq/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator!=', 'Operator', 'api/json_pointer/operator_ne/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator/', 'Operator', 'api/json_pointer/operator_slash/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator/=', 'Operator', 'api/json_pointer/operator_slasheq/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator string_t', 'Operator', 'api/json_pointer/operator_string_t/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::operator<=>', 'Operator', 'api/json_pointer/operator_spaceship/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::parent_pointer', 'Method', 'api/json_pointer/parent_pointer/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::pop_back', 'Method', 'api/json_pointer/pop_back/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::pop_front', 'Method', 'api/json_pointer/pop_front/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::push_back', 'Method', 'api/json_pointer/push_back/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::push_front', 'Method', 'api/json_pointer/push_front/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::string_t', 'Type', 'api/json_pointer/string_t/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::to_string', 'Method', 'api/json_pointer/to_string/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('json_sax', 'Class', 'api/json_sax/index.html'); @@ -163,6 +170,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('operator<<', 'Operator', 'api INSERT INTO searchIndex(name, type, path) VALUES ('operator>>', 'Operator', 'api/operator_gtgt/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json', 'Class', 'api/ordered_json/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('ordered_map', 'Class', 'api/ordered_map/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('std::formatter', 'Class', 'api/basic_json/std_formatter/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('std::hash', 'Class', 'api/basic_json/std_hash/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('std::swap', 'Function', 'api/basic_json/std_swap/index.html'); @@ -195,17 +203,20 @@ INSERT INTO searchIndex(name, type, path) VALUES ('nlohmann Namespace', 'Guide', INSERT INTO searchIndex(name, type, path) VALUES ('Types', 'Guide', 'features/types/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Types: Number Handling', 'Guide', 'features/types/number_handling/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Object Order', 'Guide', 'features/object_order/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('Performance', 'Guide', 'features/performance/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Parsing', 'Guide', 'features/parsing/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: JSON Lines', 'Guide', 'features/parsing/json_lines/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Parser Callbacks', 'Guide', 'features/parsing/parser_callbacks/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Parsing and Exceptions', 'Guide', 'features/parsing/parse_exceptions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: SAX Interface', 'Guide', 'features/parsing/sax_interface/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('Parsing: Untrusted Input', 'Guide', 'features/parsing/untrusted_input/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Runtime Assertions', 'Guide', 'features/assertions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Specializing enum conversion', 'Guide', 'features/enum_conversion/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Supported Macros', 'Guide', 'features/macros/index.html'); -- Macros INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html'); @@ -215,34 +226,59 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'a INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_20', 'Macro', 'api/macros/json_has_cpp_11/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_23', 'Macro', 'api/macros/json_has_cpp_11/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_26', 'Macro', 'api/macros/json_has_cpp_11/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_EXPERIMENTAL_FILESYSTEM', 'Macro', 'api/macros/json_has_filesystem/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_FILESYSTEM', 'Macro', 'api/macros/json_has_filesystem/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_RANGES', 'Macro', 'api/macros/json_has_ranges/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_STATIC_RTTI', 'Macro', 'api/macros/json_has_static_rtti/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_STD_FORMAT', 'Macro', 'api/macros/json_has_std_format/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_THREE_WAY_COMPARISON', 'Macro', 'api/macros/json_has_three_way_comparison/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NOEXCEPTION', 'Macro', 'api/macros/json_noexception/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_AUTOMATIC_UDLS', 'Macro', 'api/macros/json_no_automatic_udls/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_IO', 'Macro', 'api/macros/json_no_io/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_THREAD_LOCAL', 'Macro', 'api/macros/json_no_thread_local/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_PRECISE_STREAM_POSITION', 'Macro', 'api/macros/json_precise_stream_position/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_LIBRARY_VERSION_CHECK', 'Macro', 'api/macros/json_skip_library_version_check/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_UNSUPPORTED_COMPILER_CHECK', 'Macro', 'api/macros/json_skip_unsupported_compiler_check/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_NUL_HANDLING', 'Macro', 'api/macros/json_strict_nul_handling/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_THROW_USER', 'Macro', 'api/macros/json_throw_user/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api/macros/json_throw_user/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_type_intrusive/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT', 'Macro', 'api/macros/nlohmann_define_type_non_intrusive/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES', 'Macro', 'api/macros/nlohmann_define_type_with_names/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE', 'Macro', 'api/macros/nlohmann_json_namespace/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_BEGIN', 'Macro', 'api/macros/nlohmann_json_namespace_begin/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_END', 'Macro', 'api/macros/nlohmann_json_namespace_begin/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_NAMESPACE_NO_VERSION', 'Macro', 'api/macros/nlohmann_json_namespace_no_version/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_SERIALIZE_ENUM', 'Macro', 'api/macros/nlohmann_json_serialize_enum/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_SERIALIZE_ENUM_STRICT', 'Macro', 'api/macros/nlohmann_json_serialize_enum_strict/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MAJOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_MINOR', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_JSON_VERSION_PATCH', 'Macro', 'api/macros/nlohmann_json_version_major/index.html'); diff --git a/docs/mkdocs/Makefile b/docs/mkdocs/Makefile index 8c72b0e80..55ee0d509 100644 --- a/docs/mkdocs/Makefile +++ b/docs/mkdocs/Makefile @@ -1,3 +1,8 @@ +# The MkDocs 2.0 banners of Material for MkDocs and ProperDocs (via mkdocs-redirects) are printed to stderr, not +# logged, so they do not affect --strict; silence them anyway. +export NO_MKDOCS_2_WARNING := true +export DISABLE_MKDOCS_2_WARNING := true + # serve the site locally serve: style_check venv/bin/mkdocs serve @@ -8,11 +13,17 @@ serve_dirty: style_check # This target is used in the CI (ci_test_build_documentation). # This target is used by the docset Makefile. build: style_check - venv/bin/mkdocs build + venv/bin/mkdocs build --strict style_check: @cd docs ; ../venv/bin/python3 ../scripts/check_structure.py +# check that all Mermaid diagrams parse (needs Node.js) +# This target is used in the CI (ci_test_documentation_mermaid). +check_mermaid: + npm ci --prefix scripts/mermaid --ignore-scripts --no-audit --no-fund + node scripts/mermaid/check_mermaid.mjs docs + # check the links in the documentation files in docs/mkdocs link_check: ENABLED_HTMLPROOFER=true venv/bin/mkdocs build diff --git a/docs/mkdocs/docs/api/basic_json/accept.md b/docs/mkdocs/docs/api/basic_json/accept.md index 0cdcae3a8..89111f69d 100644 --- a/docs/mkdocs/docs/api/basic_json/accept.md +++ b/docs/mkdocs/docs/api/basic_json/accept.md @@ -96,7 +96,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by ## Examples -??? example +??? example "Example: (1) reading from a string" The example below demonstrates the `accept()` function reading from a string. @@ -110,6 +110,21 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/accept__string.output" ``` +??? example "Example: (2) reading from an iterator pair" + + The example below demonstrates the `accept()` function reading from an iterator pair. Only the first call covers + exactly the JSON text; the second one also covers the trailing bytes and is therefore rejected. + + ```cpp + --8<-- "examples/accept__iterator_pair.cpp" + ``` + + Output: + + ```json + --8<-- "examples/accept__iterator_pair.output" + ``` + ## See also - [parse](parse.md) - deserialize from a compatible input @@ -137,3 +152,5 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated function. + + See the [migration guide](../../integration/migration_guide.md#parsing) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/array_t.md b/docs/mkdocs/docs/api/basic_json/array_t.md index b6e41a6ad..caa0e9162 100644 --- a/docs/mkdocs/docs/api/basic_json/array_t.md +++ b/docs/mkdocs/docs/api/basic_json/array_t.md @@ -25,7 +25,7 @@ To store objects in C++, a type is defined by the template parameters explained ## Notes -#### Default type +### Default type With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default value for `array_t` is: @@ -37,7 +37,7 @@ std::vector< > ``` -#### Limits +### Limits [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: > An implementation may set limits on the maximum depth of nesting. @@ -46,7 +46,7 @@ In this class, the array's limit of nesting is not explicitly constrained. Howev introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the [`max_size`](max_size.md) function of a JSON array. -#### Storage +### Storage Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of type `#!cpp array_t*` must be dereferenced. @@ -67,6 +67,13 @@ Arrays are stored as pointers in a `basic_json` type. That is, for any access to --8<-- "examples/array_t.output" ``` +## See also + +- [object_t](object_t.md) the type used to store JSON objects +- [binary_t](binary_t.md) the type used to store binary values +- [is_array](is_array.md) checks whether the JSON value is an array +- [max_size](max_size.md) returns the maximum possible number of elements + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/at.md b/docs/mkdocs/docs/api/basic_json/at.md index 2d1042cc8..60daf38e3 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -92,6 +92,20 @@ Strong exception safety: if an exception occurs, the original value stays intact 3. Logarithmic in the size of the container. 4. Logarithmic in the size of the container. +## Notes + +!!! warning "Deprecation" + + Overload (4) also accepts a [`json_pointer`](../json_pointer/index.md) whose template argument is a `basic_json` + specialization (e.g., `nlohmann::json_pointer`) instead of a string type. This is deprecated since + version 3.11.0 and will be removed in a future major version; use `basic_json::json_pointer` (for `json`, + `nlohmann::json_pointer`) instead. + + You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated + function. + + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + ## Examples ??? example "Example: (1) access specified array element with bounds checking" diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md index 962f3e164..1a0b101bd 100644 --- a/docs/mkdocs/docs/api/basic_json/basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/basic_json.md @@ -99,6 +99,25 @@ basic_json(basic_json&& other) noexcept; elements of the pairs are treated as keys and the second elements are as values. 3. In all other cases, an array is created. + The following flowchart also takes into account what happens when `type_deduction` is `#!cpp false`, in which case + `manual_type` decides between object and array, and an object can only be forced if `init` actually matches rule 2 + (or is empty): + + ```mermaid + flowchart TD + A(["initializer_list init"]) --> B{"empty, or every element is a 2-element
array whose first element is a string?"} + B -->|"yes"| C{"type_deduction"} + B -->|"no"| D{"type_deduction"} + C -->|"true"| OBJ["create object"] + C -->|"false"| E{"manual_type"} + E -->|"object"| OBJ + E -->|"array"| ARR["create array"] + D -->|"true"| ARR + D -->|"false"| F{"manual_type"} + F -->|"array"| ARR + F -->|"object"| ERR["throw type_error.301"] + ``` + The rules aim to create the best fit between a C++ initializer list and JSON values. The rationale is as follows: 1. The empty initializer list is written as `#!cpp {}` which is exactly an empty JSON object. @@ -171,8 +190,8 @@ basic_json(basic_json&& other) noexcept; - `BasicJsonType` has different template arguments than `basic_json_t`. **Note:** For cross-`basic_json` conversions to produce correct results, the target `basic_json`'s - `object_t::key_type` and `string_t` must be directly constructible from the source `basic_json`'s - corresponding types. See the description of overload (4) above for details on what happens when + [`object_t`](object_t.md)`::key_type` and [`string_t`](string_t.md) must be directly constructible from the source + `basic_json`'s corresponding types. See the description of overload (4) above for details on what happens when this requirement is not met. `U`: @@ -347,6 +366,22 @@ basic_json(basic_json&& other) noexcept; Note the output is platform-dependent. +??? example "Example: (4) create a JSON value from another `basic_json` specialization" + + The example below shows how a `json` value is converted to an `ordered_json` value and back using the converting + constructor. Note how the original insertion order of `oj` is not restored, because it was already given up when + converting to `json`, whose `object_t` sorts by key. + + ```cpp + --8<-- "examples/basic_json__BasicJsonType.cpp" + ``` + + Output: + + ```json + --8<-- "examples/basic_json__BasicJsonType.output" + ``` + ??? example "Example: (5) create a container (array or object) from an initializer list" The example below shows how JSON values are created from initializer lists. @@ -417,6 +452,15 @@ basic_json(basic_json&& other) noexcept; --8<-- "examples/basic_json__moveconstructor.output" ``` +## See also + +- [array](array.md) create a JSON array value, forcing array creation from an initializer list even when it looks like + an object +- [object](object.md) create a JSON object value, forcing object creation from an initializer list +- [binary](binary.md) create a JSON binary array value +- [operator=](operator=.md) copy assignment operator +- [Creating JSON values](../../features/creating_values.md) - the article on creating JSON values + ## Version history 1. Since version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/begin.md b/docs/mkdocs/docs/api/basic_json/begin.md index ef623a5ff..24671cee1 100644 --- a/docs/mkdocs/docs/api/basic_json/begin.md +++ b/docs/mkdocs/docs/api/basic_json/begin.md @@ -37,6 +37,14 @@ Constant. --8<-- "examples/begin.output" ``` +## See also + +- [end](end.md) returns an iterator to one past the last element +- [cbegin](cbegin.md) returns a const iterator to the first element +- [rbegin](rbegin.md) returns a reverse iterator to the last element +- [items](items.md) returns an iteration proxy to access keys and values during range-based for loops +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/binary.md b/docs/mkdocs/docs/api/basic_json/binary.md index efd4347ca..b19b0af27 100644 --- a/docs/mkdocs/docs/api/basic_json/binary.md +++ b/docs/mkdocs/docs/api/basic_json/binary.md @@ -7,9 +7,9 @@ static basic_json binary(typename binary_t::container_type&& init); // (2) static basic_json binary(const typename binary_t::container_type& init, - std::uint8_t subtype); + typename binary_t::subtype_type subtype); static basic_json binary(typename binary_t::container_type&& init, - std::uint8_t subtype); + typename binary_t::subtype_type subtype); ``` 1. Creates a JSON binary array value from a given binary container. @@ -61,6 +61,15 @@ initialization of a binary array type, for backwards compatibility and so it doe --8<-- "examples/binary.output" ``` +## See also + +- [binary_t](binary_t.md) type for binary values +- [get_binary](get_binary.md) get a reference to the stored binary value +- [is_binary](is_binary.md) return whether the value is binary +- [byte_container_with_subtype](../byte_container_with_subtype/index.md) container for binary values with subtype +- [Binary Values](../../features/binary_values.md) - the article on binary values + ## Version history - Added in version 3.8.0. +- Changed the type of `subtype` from `std::uint8_t` to `binary_t::subtype_type` (`std::uint64_t`) in version 3.10.0. diff --git a/docs/mkdocs/docs/api/basic_json/binary_t.md b/docs/mkdocs/docs/api/basic_json/binary_t.md index 36600748a..46b0f7ae2 100644 --- a/docs/mkdocs/docs/api/basic_json/binary_t.md +++ b/docs/mkdocs/docs/api/basic_json/binary_t.md @@ -48,16 +48,16 @@ represent a byte array in modern C++. ## Notes -#### Default type +### Default type The default values for `BinaryType` is `#!cpp std::vector`. -#### Supported byte types +### Supported byte types `#!cpp std::vector`, `#!cpp std::vector`, and `#!cpp std::vector` are supported. Regardless of which of them is configured, [`dump`](dump.md) writes the bytes as the numbers 0..255. -#### Custom BinaryType behavior +### Custom BinaryType behavior When a custom `BinaryType` is configured (other than the default `#!cpp std::vector`), you can assign values of that type directly to a `basic_json` instance, and they will automatically be recognized as binary values @@ -89,12 +89,12 @@ assert(extracted == data); This automatic type detection is a convenience feature that only applies to custom (non-default) `BinaryType` configurations. The default `nlohmann::json` continues to treat `#!cpp std::vector` as arrays for backward compatibility. -#### Storage +### Storage Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the type `#!cpp binary_t*` must be dereferenced. -#### Notes on subtypes +### Notes on subtypes - CBOR - Binary values are represented as byte strings. Subtypes are written as tags. diff --git a/docs/mkdocs/docs/api/basic_json/boolean_t.md b/docs/mkdocs/docs/api/basic_json/boolean_t.md index bfb8c3426..f79a7622e 100644 --- a/docs/mkdocs/docs/api/basic_json/boolean_t.md +++ b/docs/mkdocs/docs/api/basic_json/boolean_t.md @@ -21,11 +21,11 @@ To store boolean values in C++, a type is defined by the template parameter `Bo ## Notes -#### Default type +### Default type With the default values for `BooleanType` (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`. -#### Storage +### Storage Boolean values are stored directly inside a `basic_json` type. @@ -45,6 +45,10 @@ Boolean values are stored directly inside a `basic_json` type. --8<-- "examples/boolean_t.output" ``` +## See also + +- [is_boolean](is_boolean.md) checks whether the JSON value is a boolean + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/cbegin.md b/docs/mkdocs/docs/api/basic_json/cbegin.md index 06504fee6..c0ecebcf3 100644 --- a/docs/mkdocs/docs/api/basic_json/cbegin.md +++ b/docs/mkdocs/docs/api/basic_json/cbegin.md @@ -36,6 +36,13 @@ Constant. --8<-- "examples/cbegin.output" ``` +## See also + +- [begin](begin.md) returns an iterator to the first element +- [cend](cend.md) returns a const iterator to one past the last element +- [crbegin](crbegin.md) returns a const reverse iterator to the last element +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md index 28bd99535..dfeb5d693 100644 --- a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md @@ -38,6 +38,12 @@ store --8<-- "examples/cbor_tag_handler_t.output" ``` +## See also + +- [from_cbor](from_cbor.md) deserializes a JSON value from CBOR +- [input_format_t](input_format_t.md) the enumeration of supported input formats +- [CBOR](../../features/binary_formats/cbor.md) - the article on the CBOR format + ## Version history - Added in version 3.9.0. Added value `store` in 3.10.0. diff --git a/docs/mkdocs/docs/api/basic_json/cend.md b/docs/mkdocs/docs/api/basic_json/cend.md index 3f3aa949d..0f944b48c 100644 --- a/docs/mkdocs/docs/api/basic_json/cend.md +++ b/docs/mkdocs/docs/api/basic_json/cend.md @@ -36,6 +36,13 @@ Constant. --8<-- "examples/cend.output" ``` +## See also + +- [end](end.md) returns an iterator to one past the last element +- [cbegin](cbegin.md) returns a const iterator to the first element +- [crend](crend.md) returns a const reverse iterator to one before the first element +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/clear.md b/docs/mkdocs/docs/api/basic_json/clear.md index a242b678a..92043e978 100644 --- a/docs/mkdocs/docs/api/basic_json/clear.md +++ b/docs/mkdocs/docs/api/basic_json/clear.md @@ -52,6 +52,11 @@ All iterators, pointers, and references related to this container are invalidate --8<-- "examples/clear.output" ``` +## See also + +- [erase](erase.md) removes elements from a JSON value +- [empty](empty.md) checks whether the JSON value has no elements + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index 84b3c443c..73bcf6f0c 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -67,6 +67,18 @@ Logarithmic in the size of the JSON object. If `#!cpp j.contains(x)` returns `#!c true` for a key or JSON pointer `x`, then it is safe to call `j[x]`. +!!! warning "Deprecation" + + Overload (3) also accepts a [`json_pointer`](../json_pointer/index.md) whose template argument is a `basic_json` + specialization (e.g., `nlohmann::json_pointer`) instead of a string type. This is deprecated since + version 3.11.0 and will be removed in a future major version; use `basic_json::json_pointer` (for `json`, + `nlohmann::json_pointer`) instead. + + You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated + function. + + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + ## Examples ??? example "Example: (1) check with key" diff --git a/docs/mkdocs/docs/api/basic_json/crbegin.md b/docs/mkdocs/docs/api/basic_json/crbegin.md index 95680fb3a..545f86044 100644 --- a/docs/mkdocs/docs/api/basic_json/crbegin.md +++ b/docs/mkdocs/docs/api/basic_json/crbegin.md @@ -36,6 +36,13 @@ Constant. --8<-- "examples/crbegin.output" ``` +## See also + +- [crend](crend.md) returns a const reverse iterator to one before the first element +- [rbegin](rbegin.md) returns a reverse iterator to the last element +- [cbegin](cbegin.md) returns a const iterator to the first element +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/crend.md b/docs/mkdocs/docs/api/basic_json/crend.md index 19c581dfe..56cc40c52 100644 --- a/docs/mkdocs/docs/api/basic_json/crend.md +++ b/docs/mkdocs/docs/api/basic_json/crend.md @@ -37,6 +37,13 @@ Constant. --8<-- "examples/crend.output" ``` +## See also + +- [crbegin](crbegin.md) returns a const reverse iterator to the last element +- [rend](rend.md) returns a reverse iterator to one before the first element +- [cend](cend.md) returns a const iterator to one past the last element +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md b/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md index 8a237f662..2c3a50217 100644 --- a/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md +++ b/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md @@ -30,6 +30,11 @@ The actual comparator used depends on [`object_t`](object_t.md) and can be obtai --8<-- "examples/default_object_comparator_t.output" ``` +## See also + +- [object_comparator_t](object_comparator_t.md) the comparator actually used by `object_t` +- [object_t](object_t.md) the type used to store JSON objects + ## Version history - Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/emplace.md b/docs/mkdocs/docs/api/basic_json/emplace.md index 18286e83f..26044a597 100644 --- a/docs/mkdocs/docs/api/basic_json/emplace.md +++ b/docs/mkdocs/docs/api/basic_json/emplace.md @@ -31,7 +31,8 @@ a `#!cpp bool` denoting whether the insertion took place. ## Exception safety -Strong guarantee: if an exception is thrown, there are no changes to any JSON value. +Strong guarantee: if an exception is thrown, there are no changes to any JSON value. As an exception, a `#!json null` +value is converted to an empty object before the element is added and keeps that type if adding the element throws. ## Exceptions diff --git a/docs/mkdocs/docs/api/basic_json/emplace_back.md b/docs/mkdocs/docs/api/basic_json/emplace_back.md index 516a66e5d..782b61f36 100644 --- a/docs/mkdocs/docs/api/basic_json/emplace_back.md +++ b/docs/mkdocs/docs/api/basic_json/emplace_back.md @@ -28,6 +28,11 @@ iterator is invalidated. reference to the inserted element +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes to any JSON value. As an exception, a `#!json null` +value is converted to an empty array before the element is added and keeps that type if adding the element throws. + ## Exceptions Throws [`type_error.311`](../../home/exceptions.md#jsonexceptiontype_error311) when called on a type other than JSON diff --git a/docs/mkdocs/docs/api/basic_json/empty.md b/docs/mkdocs/docs/api/basic_json/empty.md index 8d566738d..1d393d76e 100644 --- a/docs/mkdocs/docs/api/basic_json/empty.md +++ b/docs/mkdocs/docs/api/basic_json/empty.md @@ -60,6 +60,11 @@ itself is empty which is `#!cpp false` in the case of a string. --8<-- "examples/empty.output" ``` +## See also + +- [size](size.md) returns the number of elements +- [clear](clear.md) clears the content and resets the value to the default value + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/end.md b/docs/mkdocs/docs/api/basic_json/end.md index 179ce9e67..bb735c6df 100644 --- a/docs/mkdocs/docs/api/basic_json/end.md +++ b/docs/mkdocs/docs/api/basic_json/end.md @@ -37,6 +37,13 @@ Constant. --8<-- "examples/end.output" ``` +## See also + +- [begin](begin.md) returns an iterator to the first element +- [cend](cend.md) returns a const iterator to one past the last element +- [rend](rend.md) returns a reverse iterator to one before the first element +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/error_handler_t.md b/docs/mkdocs/docs/api/basic_json/error_handler_t.md index f20c33c03..51dc6510f 100644 --- a/docs/mkdocs/docs/api/basic_json/error_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/error_handler_t.md @@ -37,6 +37,11 @@ ignore --8<-- "examples/error_handler_t.output" ``` +## See also + +- [dump](dump.md) serializes a JSON value, with an `error_handler_t` parameter to configure invalid UTF-8 handling +- [Handling invalid UTF-8](../../features/serialization.md#handling-invalid-utf-8) - the article on handling invalid UTF-8 + ## Version history - Added in version 3.4.0. diff --git a/docs/mkdocs/docs/api/basic_json/flatten.md b/docs/mkdocs/docs/api/basic_json/flatten.md index 7b26a8900..a232a538a 100644 --- a/docs/mkdocs/docs/api/basic_json/flatten.md +++ b/docs/mkdocs/docs/api/basic_json/flatten.md @@ -27,7 +27,7 @@ Empty objects and arrays are flattened to `#!json null` and will not be reconstr ## Examples -??? example +??? example "Example: flatten a JSON object" The following code shows how a JSON object is flattened to an object whose keys consist of JSON pointers. @@ -41,6 +41,21 @@ Empty objects and arrays are flattened to `#!json null` and will not be reconstr --8<-- "examples/flatten.output" ``` +??? example "Example: empty objects and arrays are flattened to `#!json null`" + + The following code shows that an empty object and an empty array are both flattened to `#!json null`, and that + `unflatten()` restores them as `#!json null` rather than as empty containers. + + ```cpp + --8<-- "examples/flatten__empty.cpp" + ``` + + Output: + + ```json + --8<-- "examples/flatten__empty.output" + ``` + ## See also - [unflatten](unflatten.md) the reverse function diff --git a/docs/mkdocs/docs/api/basic_json/from_bson.md b/docs/mkdocs/docs/api/basic_json/from_bson.md index cf022aeb6..9dfd9dc18 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bson.md +++ b/docs/mkdocs/docs/api/basic_json/from_bson.md @@ -123,3 +123,5 @@ Linear in the size of the input. You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated function. + + See the [migration guide](../../integration/migration_guide.md#parsing) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/from_cbor.md b/docs/mkdocs/docs/api/basic_json/from_cbor.md index 8c1062da8..791c183bb 100644 --- a/docs/mkdocs/docs/api/basic_json/from_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/from_cbor.md @@ -133,3 +133,5 @@ Linear in the size of the input. You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated function. + + See the [migration guide](../../integration/migration_guide.md#parsing) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/from_msgpack.md b/docs/mkdocs/docs/api/basic_json/from_msgpack.md index e41edfe7e..395512acb 100644 --- a/docs/mkdocs/docs/api/basic_json/from_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/from_msgpack.md @@ -125,3 +125,5 @@ Linear in the size of the input. You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated function. + + See the [migration guide](../../integration/migration_guide.md#parsing) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/from_ubjson.md b/docs/mkdocs/docs/api/basic_json/from_ubjson.md index 0d060c750..1ad076588 100644 --- a/docs/mkdocs/docs/api/basic_json/from_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/from_ubjson.md @@ -124,3 +124,5 @@ Linear in the size of the input. You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated function. + + See the [migration guide](../../integration/migration_guide.md#parsing) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/get.md b/docs/mkdocs/docs/api/basic_json/get.md index 8329de919..0dcd3189f 100644 --- a/docs/mkdocs/docs/api/basic_json/get.md +++ b/docs/mkdocs/docs/api/basic_json/get.md @@ -13,16 +13,16 @@ BasicJsonType get() const; // (3) template -PointerType get_ptr(); +PointerType get() noexcept; template -constexpr const PointerType get_ptr() const noexcept; +const PointerType get() const noexcept; // constexpr since C++14 ``` 1. Explicit type conversion between the JSON value and a compatible value which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). The value is converted by - calling the `json_serializer` `from_json()` method. + calling the [`json_serializer`](json_serializer.md) `from_json()` method. The function is equivalent to executing ```cpp @@ -84,6 +84,12 @@ constexpr const PointerType get_ptr() const noexcept; 3. pointer to the internally stored JSON value if the requested pointer type fits to the JSON value; `#!cpp nullptr` otherwise +## Exception safety + +Depends on what `json_serializer` `from_json()` method throws for overloads (1) and (2); the JSON value +itself is never modified, since `get()` is a `#!cpp const` member function. No-throw guarantee for overload (3): this +function never throws exceptions. + ## Exceptions Depends on what `json_serializer` `from_json()` method throws @@ -123,13 +129,13 @@ overload (3). ## Examples -??? example +??? example "Example: (1) explicit conversion to compatible types" The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers, (2) A JSON array can be converted to a standard `std::vector`, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`. + associative containers such as `std::map`. ```cpp --8<-- "examples/get__ValueType_const.cpp" @@ -141,7 +147,21 @@ overload (3). --8<-- "examples/get__ValueType_const.output" ``` -??? example +??? example "Example: (2) explicit conversion to another `basic_json` specialization" + + The example below shows how a `json` value is converted to an `ordered_json` value using `get()`. + + ```cpp + --8<-- "examples/get__BasicJsonType.cpp" + ``` + + Output: + + ```json + --8<-- "examples/get__BasicJsonType.output" + ``` + +??? example "Example: (3) explicit pointer access to the stored value" The example below shows how pointers to internal values of a JSON value can be requested. Note that no type conversions are made and a `#cpp nullptr` is returned if the value and the requested pointer type does not match. diff --git a/docs/mkdocs/docs/api/basic_json/get_allocator.md b/docs/mkdocs/docs/api/basic_json/get_allocator.md index 07a4d8456..46f71dff6 100644 --- a/docs/mkdocs/docs/api/basic_json/get_allocator.md +++ b/docs/mkdocs/docs/api/basic_json/get_allocator.md @@ -10,6 +10,14 @@ Returns the allocator associated with the container. associated allocator +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes to any JSON value. + +## Complexity + +Constant. + ## Examples ??? example @@ -26,6 +34,11 @@ associated allocator --8<-- "examples/get_allocator.output" ``` +## See also + +- [basic_json](index.md#template-parameters) the class template, with `AllocatorType` as one of its template parameters +- [Template Parameter Requirements](../../features/types/template_parameters.md#allocatortype) - the requirements for `AllocatorType` + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/get_to.md b/docs/mkdocs/docs/api/basic_json/get_to.md index c50c077b0..1839e959a 100644 --- a/docs/mkdocs/docs/api/basic_json/get_to.md +++ b/docs/mkdocs/docs/api/basic_json/get_to.md @@ -8,7 +8,7 @@ ValueType& get_to(ValueType& v) const noexcept( ``` Explicit type conversion between the JSON value and a compatible value. The value is filled into the input parameter by -calling the `json_serializer` `from_json()` method. +calling the [`json_serializer`](json_serializer.md) `from_json()` method. The function is equivalent to executing ```cpp @@ -34,6 +34,11 @@ the compiler reports that no matching `get_to` was found. the input parameter, allowing chaining calls +## Exception safety + +Depends on what `json_serializer` `from_json()` method throws; the JSON value itself is never modified, +since `get_to()` is a `#!cpp const` member function. + ## Exceptions Depends on what `json_serializer` `from_json()` method throws @@ -49,7 +54,7 @@ Depends on the `json_serializer::from_json()` implementation. The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers, (2) A JSON array can be converted to a standard `#!cpp std::vector`, (3) A JSON object can be converted to C++ associative containers such as - `#cpp std::unordered_map`. + `#!cpp std::map`. ```cpp --8<-- "examples/get_to.cpp" diff --git a/docs/mkdocs/docs/api/basic_json/input_format_t.md b/docs/mkdocs/docs/api/basic_json/input_format_t.md index 407b43427..34a80e3f7 100644 --- a/docs/mkdocs/docs/api/basic_json/input_format_t.md +++ b/docs/mkdocs/docs/api/basic_json/input_format_t.md @@ -51,6 +51,11 @@ bon8 --8<-- "examples/sax_parse__binary.output" ``` +## See also + +- [sax_parse](sax_parse.md) generic SAX parse interface, taking an `input_format_t` to select the input format +- [cbor_tag_handler_t](cbor_tag_handler_t.md) configures how CBOR tags are treated while parsing + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/basic_json/insert.md b/docs/mkdocs/docs/api/basic_json/insert.md index ff9082b56..12009fbaa 100644 --- a/docs/mkdocs/docs/api/basic_json/insert.md +++ b/docs/mkdocs/docs/api/basic_json/insert.md @@ -109,11 +109,11 @@ Strong exception safety: if an exception occurs, the original value stays intact 2. Linear in `cnt` plus linear in the distance between `pos` and end of the container. 3. Linear in `#!cpp std::distance(first, last)` plus linear in the distance between `pos` and end of the container. 4. Linear in `ilist.size()` plus linear in the distance between `pos` and end of the container. -5. Logarithmic: `O(N*log(size() + N))`, where `N` is the number of elements to insert. +5. `O(N*log(size() + N))`, where `N` is the number of elements to insert. ## Examples -??? example "Example (1): insert element into array" +??? example "Example: (1) insert element into array" The example shows how `insert()` is used. @@ -127,7 +127,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/insert.output" ``` -??? example "Example (2): insert copies of element into array" +??? example "Example: (2) insert copies of element into array" The example shows how `insert()` is used. @@ -141,7 +141,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/insert__count.output" ``` -??? example "Example (3): insert a range of elements into an array" +??? example "Example: (3) insert a range of elements into an array" The example shows how `insert()` is used. @@ -155,7 +155,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/insert__range.output" ``` -??? example "Example (4): insert elements from an initializer list into an array" +??? example "Example: (4) insert elements from an initializer list into an array" The example shows how `insert()` is used. @@ -169,7 +169,7 @@ Strong exception safety: if an exception occurs, the original value stays intact --8<-- "examples/insert__ilist.output" ``` -??? example "Example (5): insert a range of elements into an object" +??? example "Example: (5) insert a range of elements into an object" The example shows how `insert()` is used. diff --git a/docs/mkdocs/docs/api/basic_json/is_array.md b/docs/mkdocs/docs/api/basic_json/is_array.md index 64468c357..af68bdc0d 100644 --- a/docs/mkdocs/docs/api/basic_json/is_array.md +++ b/docs/mkdocs/docs/api/basic_json/is_array.md @@ -34,6 +34,13 @@ Constant. --8<-- "examples/is_array.output" ``` +## See also + +- [is_object](is_object.md) checks whether the JSON value is an object +- [is_structured](is_structured.md) checks whether the JSON value is structured (array or object) +- [type](type.md) returns the type of the JSON value +- [array_t](array_t.md) the type used to store JSON arrays + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/is_binary.md b/docs/mkdocs/docs/api/basic_json/is_binary.md index 2a42e5edf..ecd853fcd 100644 --- a/docs/mkdocs/docs/api/basic_json/is_binary.md +++ b/docs/mkdocs/docs/api/basic_json/is_binary.md @@ -34,6 +34,12 @@ Constant. --8<-- "examples/is_binary.output" ``` +## See also + +- [is_primitive](is_primitive.md) checks whether the JSON value is primitive +- [binary_t](binary_t.md) the type used to store binary values +- [get_binary](get_binary.md) returns a reference to the stored binary value + ## Version history - Added in version 3.8.0. diff --git a/docs/mkdocs/docs/api/basic_json/is_boolean.md b/docs/mkdocs/docs/api/basic_json/is_boolean.md index dc41d84bd..69e85cbfe 100644 --- a/docs/mkdocs/docs/api/basic_json/is_boolean.md +++ b/docs/mkdocs/docs/api/basic_json/is_boolean.md @@ -34,6 +34,11 @@ Constant. --8<-- "examples/is_boolean.output" ``` +## See also + +- [boolean_t](boolean_t.md) the type used to store JSON booleans +- [is_primitive](is_primitive.md) checks whether the JSON value is primitive + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/is_discarded.md b/docs/mkdocs/docs/api/basic_json/is_discarded.md index 56fae2a49..9ab0ab38a 100644 --- a/docs/mkdocs/docs/api/basic_json/is_discarded.md +++ b/docs/mkdocs/docs/api/basic_json/is_discarded.md @@ -55,7 +55,7 @@ with `allow_exceptions` set to `#!cpp false`: a parse error then yields a discar ## Examples -??? example +??? example "Example: `is_discarded()` for ordinary JSON values" The following code exemplifies `is_discarded()` for all JSON types. @@ -69,6 +69,22 @@ with `allow_exceptions` set to `#!cpp false`: a parse error then yields a discar --8<-- "examples/is_discarded.output" ``` +??? example "Example: discarded values from parsing" + + The following code shows the two situations in which a discarded value can be observed: parsing invalid JSON with + `allow_exceptions` set to `#!cpp false`, and a parser callback that discards the top-level value (which is replaced + by `#!json null` and therefore does *not* remain discarded). + + ```cpp + --8<-- "examples/is_discarded__parse.cpp" + ``` + + Output: + + ```json + --8<-- "examples/is_discarded__parse.output" + ``` + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/is_null.md b/docs/mkdocs/docs/api/basic_json/is_null.md index d080ad32f..e90a6c7ea 100644 --- a/docs/mkdocs/docs/api/basic_json/is_null.md +++ b/docs/mkdocs/docs/api/basic_json/is_null.md @@ -34,6 +34,13 @@ Constant. --8<-- "examples/is_null.output" ``` +## See also + +- [is_array](is_array.md) checks whether the JSON value is an array +- [is_object](is_object.md) checks whether the JSON value is an object +- [type](type.md) returns the type of the JSON value +- [value_t](value_t.md) the enumeration of JSON types + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/is_object.md b/docs/mkdocs/docs/api/basic_json/is_object.md index 04457013b..17776a8bc 100644 --- a/docs/mkdocs/docs/api/basic_json/is_object.md +++ b/docs/mkdocs/docs/api/basic_json/is_object.md @@ -34,6 +34,13 @@ Constant. --8<-- "examples/is_object.output" ``` +## See also + +- [is_array](is_array.md) checks whether the JSON value is an array +- [is_structured](is_structured.md) checks whether the JSON value is structured (array or object) +- [type](type.md) returns the type of the JSON value +- [object_t](object_t.md) the type used to store JSON objects + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/is_string.md b/docs/mkdocs/docs/api/basic_json/is_string.md index b82c92465..d91eaf0a8 100644 --- a/docs/mkdocs/docs/api/basic_json/is_string.md +++ b/docs/mkdocs/docs/api/basic_json/is_string.md @@ -34,6 +34,12 @@ Constant. --8<-- "examples/is_string.output" ``` +## See also + +- [is_primitive](is_primitive.md) checks whether the JSON value is primitive +- [type](type.md) returns the type of the JSON value +- [string_t](string_t.md) the type used to store JSON strings + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/items.md b/docs/mkdocs/docs/api/basic_json/items.md index 3f3d1f2ca..65637001b 100644 --- a/docs/mkdocs/docs/api/basic_json/items.md +++ b/docs/mkdocs/docs/api/basic_json/items.md @@ -114,3 +114,5 @@ When iterating over an array, `key()` will return the index of the element as st You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated function. + + See the [migration guide](../../integration/migration_guide.md#miscellaneous-functions) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md index 7add54098..0d1abc9d4 100644 --- a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md +++ b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md @@ -14,12 +14,12 @@ Examples of such functionality might be metadata, additional member functions (e ## Notes -#### Default type +### Default type The default value for `CustomBaseClass` is `void`. In this case, an [empty base class](https://en.cppreference.com/w/cpp/language/ebo) is used and no additional functionality is injected. -#### Limitations +### Limitations The type `CustomBaseClass` has to be a default-constructible, non-`final` class. `basic_json` only supports copy/move construction/assignment if `CustomBaseClass` does so as well. @@ -43,6 +43,10 @@ A `CustomBaseClass` with non-static data members forfeits `basic_json`'s --8<-- "examples/json_base_class_t.output" ``` +## See also + +- [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass) - the requirements for `CustomBaseClass` + ## Version history - Added in version 3.12.0. diff --git a/docs/mkdocs/docs/api/basic_json/json_serializer.md b/docs/mkdocs/docs/api/basic_json/json_serializer.md index b92cf3d17..20e9cfd44 100644 --- a/docs/mkdocs/docs/api/basic_json/json_serializer.md +++ b/docs/mkdocs/docs/api/basic_json/json_serializer.md @@ -15,11 +15,11 @@ using json_serializer = JSONSerializer; ## Notes -#### Default type +### Default type The default values for `json_serializer` is [`adl_serializer`](../adl_serializer/index.md). -#### Requirements +### Requirements A custom serializer must provide `#!cpp static void to_json(basic_json&, T)` for every type it serializes, and either `#!cpp static void from_json(const basic_json&, T&)` or `#!cpp static T from_json(const basic_json&)` for every type it @@ -42,6 +42,13 @@ deserializes. See [Template Parameter Requirements](../../features/types/templat --8<-- "examples/from_json__non_default_constructible.output" ``` +## See also + +- [adl_serializer](../adl_serializer/index.md) the default `json_serializer` +- [get](get.md) explicit type conversion using the `json_serializer`'s `from_json()` method +- [get_to](get_to.md) explicit type conversion into a variable using the `json_serializer`'s `from_json()` method +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - the article on converting between JSON values and arbitrary types + ## Version history - Since version 2.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/max_size.md b/docs/mkdocs/docs/api/basic_json/max_size.md index 4c0c57520..5896a2afd 100644 --- a/docs/mkdocs/docs/api/basic_json/max_size.md +++ b/docs/mkdocs/docs/api/basic_json/max_size.md @@ -54,6 +54,12 @@ string elements the JSON value can store which is `1`. Note the output is platform-dependent. +## See also + +- [size](size.md) returns the number of elements +- [array_t](array_t.md) the type used to store JSON arrays +- [object_t](object_t.md) the type used to store JSON objects + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/merge_patch.md b/docs/mkdocs/docs/api/basic_json/merge_patch.md index 634d889d3..dbda23f42 100644 --- a/docs/mkdocs/docs/api/basic_json/merge_patch.md +++ b/docs/mkdocs/docs/api/basic_json/merge_patch.md @@ -33,6 +33,10 @@ Thereby, `Target` is the current object; that is, the patch is applied to the cu `apply_patch` (in) : the patch to apply +## Exception safety + +Basic guarantee: if an exception is thrown during the operation, the JSON value may be partially modified. + ## Complexity Linear in the lengths of `apply_patch`. diff --git a/docs/mkdocs/docs/api/basic_json/number_float_t.md b/docs/mkdocs/docs/api/basic_json/number_float_t.md index 83c7011c5..8419a392d 100644 --- a/docs/mkdocs/docs/api/basic_json/number_float_t.md +++ b/docs/mkdocs/docs/api/basic_json/number_float_t.md @@ -32,18 +32,18 @@ type to use. ## Notes -#### Default type +### Default type With the default values for `NumberFloatType` (`double`), the default value for `number_float_t` is `#!cpp double`. -#### Default behavior +### Default behavior - The restrictions about leading zeros are not enforced in C++. Instead, leading zeros in floating-point literals will be ignored. Internally, the value will be stored as a decimal number. For instance, the C++ floating-point literal `01.2` will be serialized to `1.2`. During deserialization, leading zeros yield an error. - Not-a-number (NaN) values will be serialized to `null`. -#### Limits +### Limits [RFC 8259](https://tools.ietf.org/html/rfc8259) states: > This specification allows implementations to set limits on the range and precision of numbers accepted. Since software @@ -55,7 +55,7 @@ This implementation does exactly follow this approach, as it uses double precisi smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally and be serialized to `null`. -#### Storage +### Storage Floating-point number values are stored directly inside a `basic_json` type. @@ -75,6 +75,13 @@ Floating-point number values are stored directly inside a `basic_json` type. --8<-- "examples/number_float_t.output" ``` +## See also + +- [number_integer_t](number_integer_t.md) the type used to store JSON integer numbers +- [number_unsigned_t](number_unsigned_t.md) the type used to store JSON unsigned integer numbers +- [is_number_float](is_number_float.md) checks whether the JSON value is a floating-point number +- [Number Handling](../../features/types/number_handling.md) - the article on number handling + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/number_integer_t.md b/docs/mkdocs/docs/api/basic_json/number_integer_t.md index 9b1d7ae74..d77c6a24d 100644 --- a/docs/mkdocs/docs/api/basic_json/number_integer_t.md +++ b/docs/mkdocs/docs/api/basic_json/number_integer_t.md @@ -29,18 +29,18 @@ to use. ## Notes -#### Default type +### Default type With the default values for `NumberIntegerType` (`std::int64_t`), the default value for `number_integer_t` is `#!cpp std::int64_t`. -#### Default behavior +### Default behavior - The restrictions about leading zeros are not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as an octal number. Internally, the value will be stored as a decimal number. For instance, the C++ integer literal `010` will be serialized to `8`. During deserialization, leading zeros yield an error. -#### Limits +### Limits [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: > An implementation may set limits on the range and precision of numbers. @@ -57,7 +57,7 @@ will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [ As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is interoperable. -#### Storage +### Storage Integer number values are stored directly inside a `basic_json` type. @@ -77,6 +77,13 @@ Integer number values are stored directly inside a `basic_json` type. --8<-- "examples/number_integer_t.output" ``` +## See also + +- [number_unsigned_t](number_unsigned_t.md) the type used to store JSON unsigned integer numbers +- [number_float_t](number_float_t.md) the type used to store JSON floating-point numbers +- [is_number_integer](is_number_integer.md) checks whether the JSON value is a signed integer number +- [Number Handling](../../features/types/number_handling.md) - the article on number handling + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md b/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md index f4799b2e9..774fda638 100644 --- a/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md +++ b/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md @@ -30,18 +30,18 @@ the type to use. ## Notes -#### Default type +### Default type With the default values for `NumberUnsignedType` (`std::uint64_t`), the default value for `number_unsigned_t` is `#!cpp std::uint64_t`. -#### Default behavior +### Default behavior - The restrictions about leading zeros are not enforced in C++. Instead, leading zeros in integer literals lead to an interpretation as an octal number. Internally, the value will be stored as a decimal number. For instance, the C++ integer literal `010` will be serialized to `8`. During deserialization, leading zeros yield an error. -#### Limits +### Limits [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: > An implementation may set limits on the range and precision of numbers. @@ -58,7 +58,7 @@ as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_ As this range is a subrange (when considered in conjunction with the `number_integer_t` type) of the exactly supported range [0, UINT64_MAX], this class's integer type is interoperable. -#### Storage +### Storage Integer number values are stored directly inside a `basic_json` type. @@ -78,6 +78,13 @@ Integer number values are stored directly inside a `basic_json` type. --8<-- "examples/number_unsigned_t.output" ``` +## See also + +- [number_integer_t](number_integer_t.md) the type used to store JSON integer numbers +- [number_float_t](number_float_t.md) the type used to store JSON floating-point numbers +- [is_number_unsigned](is_number_unsigned.md) checks whether the JSON value is an unsigned integer number +- [Number Handling](../../features/types/number_handling.md) - the article on number handling + ## Version history - Added in version 2.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/object_comparator_t.md b/docs/mkdocs/docs/api/basic_json/object_comparator_t.md index bbda0a0e7..7f2982703 100644 --- a/docs/mkdocs/docs/api/basic_json/object_comparator_t.md +++ b/docs/mkdocs/docs/api/basic_json/object_comparator_t.md @@ -25,6 +25,11 @@ and [`default_object_comparator_t`](default_object_comparator_t.md) otherwise. --8<-- "examples/object_comparator_t.output" ``` +## See also + +- [object_t](object_t.md) the type used to store JSON objects +- [default_object_comparator_t](default_object_comparator_t.md) the fallback comparator used when `object_t` has no `key_compare` member type + ## Version history - Added in version 3.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/object_t.md b/docs/mkdocs/docs/api/basic_json/object_t.md index 6ce393a1d..204e5ab28 100644 --- a/docs/mkdocs/docs/api/basic_json/object_t.md +++ b/docs/mkdocs/docs/api/basic_json/object_t.md @@ -33,7 +33,7 @@ To store objects in C++, a type is defined by the template parameters described ## Notes -#### Default type +### Default type With the default values for `ObjectType` (`std::map`), `StringType` (`std::string`), and `AllocatorType` (`std::allocator`), the default value for `object_t` is: @@ -58,7 +58,7 @@ std::map< See [`default_object_comparator_t`](default_object_comparator_t.md) for more information. -#### Behavior +### Behavior The choice of `object_t` influences the behavior of the JSON class. With the default type, objects have the following behavior: @@ -76,7 +76,7 @@ behavior: that they will not be affected by these differences. For instance, `#!json {"b": 1, "a": 2}` and `#!json {"a": 2, "b": 1}` will be treated as equal. -#### Limits +### Limits [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: > An implementation may set limits on the maximum depth of nesting. @@ -85,12 +85,12 @@ In this class, the object's limit of nesting is not explicitly constrained. Howe introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the [`max_size`](max_size.md) function of a JSON object. -#### Storage +### Storage Objects are stored as pointers in a `basic_json` type. That is, for any access to object values, a pointer of type `object_t*` must be dereferenced. -#### Object key order +### Object key order The order name/value pairs are added to the object are *not* preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in @@ -98,10 +98,10 @@ alphabetical order as `std::map` with `std::less` is used by default. Please not [RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON objects. -#### Cross-`basic_json` conversion requirements +### Cross-`basic_json` conversion requirements When converting an object from one `basic_json` specialization to another via the -[converting constructor](basic_json.md#overload-4), the target `object_t`'s `key_type` must be +[converting constructor](basic_json.md) (overload 4), the target `object_t`'s `key_type` must be directly constructible from the source `basic_json`'s `string_t` type (or more generally, from the source object's key type). If this requirement is not met, the conversion does not fail; instead, the object is silently converted as an array of key-value pairs, which is incorrect. See @@ -123,6 +123,13 @@ the object is silently converted as an array of key-value pairs, which is incorr --8<-- "examples/object_t.output" ``` +## See also + +- [array_t](array_t.md) the type used to store JSON arrays +- [string_t](string_t.md) the type used to store JSON strings +- [object_comparator_t](object_comparator_t.md) the comparator used to order object keys +- [Object Order](../../features/object_order.md) - the article on object key ordering + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator+=.md b/docs/mkdocs/docs/api/basic_json/operator+=.md index f296d5c4a..a3dc61b64 100644 --- a/docs/mkdocs/docs/api/basic_json/operator+=.md +++ b/docs/mkdocs/docs/api/basic_json/operator+=.md @@ -48,6 +48,12 @@ invalidates all iterators and all references. `#!cpp *this` +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes to any JSON value. As an exception, a `#!json null` +value is converted to an empty array or object before the element is added and keeps that type if adding the element +throws. + ## Exceptions 1. Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index 21c4fe8c0..23926d5f6 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -136,6 +136,18 @@ Strong exception safety: if an exception occurs, the original value stays intact while `/foo/one/one/one` creates nested objects. This is not specified by the JSON Pointer RFC; it is this library's own, intentional disambiguation rule. See also [JSON Pointer](../../features/json_pointer.md). +!!! warning "Deprecation" + + Overload (4) also accepts a [`json_pointer`](../json_pointer/index.md) whose template argument is a `basic_json` + specialization (e.g., `nlohmann::json_pointer`) instead of a string type. This is deprecated since + version 3.11.0 and will be removed in a future major version; use `basic_json::json_pointer` (for `json`, + `nlohmann::json_pointer`) instead. + + You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated + function. + + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + ## Examples ??? example "Example: (1) access specified array element" diff --git a/docs/mkdocs/docs/api/basic_json/operator_ValueType.md b/docs/mkdocs/docs/api/basic_json/operator_ValueType.md index 231a33df4..48abfb3b5 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ValueType.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ValueType.md @@ -17,6 +17,11 @@ Implicit type conversion between the JSON value and a compatible value. The call copy of the JSON value, converted to `ValueType` +## Exception safety + +Depends on what `json_serializer` `from_json()` method throws; the JSON value itself is never modified, +since `#!cpp operator ValueType()` is a `#!cpp const` member function that only calls [`get()`](get.md). + ## Exceptions Depends on what `json_serializer` `from_json()` method throws @@ -56,6 +61,8 @@ Linear in the size of the JSON value. [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) to `0` and replace any implicit conversions with calls to [`get`](../basic_json/get.md). + See the [migration guide](../../integration/migration_guide.md#replace-implicit-conversions) for how to update existing code. + ## Examples ??? example @@ -63,7 +70,7 @@ Linear in the size of the JSON value. The example below shows several conversions from JSON values to other types. There are a few things to note: (1) Floating-point numbers can be converted to integers, (2) A JSON array can be converted to a standard `std::vector`, (3) A JSON object can be converted to C++ associative containers such as - `std::unordered_map`. + `std::map`. ```cpp --8<-- "examples/operator__ValueType.cpp" diff --git a/docs/mkdocs/docs/api/basic_json/operator_eq.md b/docs/mkdocs/docs/api/basic_json/operator_eq.md index b575622d1..26eda720f 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json/operator_eq.md @@ -134,7 +134,7 @@ Linear. ## Examples -??? example +??? example "Example: (1) compare JSON values" The example demonstrates comparing several JSON types. @@ -148,7 +148,7 @@ Linear. --8<-- "examples/operator__equal.output" ``` -??? example +??? example "Example: (2) compare JSON values with `#!cpp nullptr`" The example demonstrates comparing several JSON types against the null pointer (JSON `#!json null`). diff --git a/docs/mkdocs/docs/api/basic_json/operator_ge.md b/docs/mkdocs/docs/api/basic_json/operator_ge.md index 9ae6ada86..f7899beab 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ge.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ge.md @@ -60,6 +60,16 @@ Linear. Since C++20 overload resolution will consider the _rewritten candidate_ generated from [`operator<=>`](operator_spaceship.md). +!!! warning "Deprecation" + + If [`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../macros/json_use_legacy_discarded_value_comparison.md) is + defined to `1`, the library declares a member `#!cpp bool operator>=(const_reference rhs) const noexcept` in + C++20 mode to emulate the legacy comparison of discarded values. This member is deprecated since version 3.11.0, + together with the legacy comparison behavior. + + See the [migration guide](../../integration/migration_guide.md#miscellaneous-functions) for how to update existing + code. + ## Examples ??? example diff --git a/docs/mkdocs/docs/api/basic_json/operator_le.md b/docs/mkdocs/docs/api/basic_json/operator_le.md index 9dfa4e1e0..4334fe35e 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_le.md +++ b/docs/mkdocs/docs/api/basic_json/operator_le.md @@ -61,6 +61,16 @@ Linear. Since C++20 overload resolution will consider the _rewritten candidate_ generated from [`operator<=>`](operator_spaceship.md). +!!! warning "Deprecation" + + If [`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../macros/json_use_legacy_discarded_value_comparison.md) is + defined to `1`, the library declares a member `#!cpp bool operator<=(const_reference rhs) const noexcept` in + C++20 mode to emulate the legacy comparison of discarded values. This member is deprecated since version 3.11.0, + together with the legacy comparison behavior. + + See the [migration guide](../../integration/migration_guide.md#miscellaneous-functions) for how to update existing + code. + ## Examples ??? example diff --git a/docs/mkdocs/docs/api/basic_json/operator_ne.md b/docs/mkdocs/docs/api/basic_json/operator_ne.md index 5abb4a5af..a8c6fecc2 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ne.md @@ -9,17 +9,9 @@ bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; // (2) template bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; // (2) - -// since C++20 -class basic_json { - bool operator!=(const_reference rhs) const noexcept; // (1) - - template - bool operator!=(ScalarType rhs) const noexcept; // (2) -}; ``` -1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)` (until C++20) or `#!cpp !(*this == rhs)` (since C++20). +1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)`. - This means the comparison is simply the logical negation of `operator==`, including for special values like `NaN` and `discarded`. 2. Compares a JSON value and a scalar or a scalar and a JSON value for inequality by converting the scalar to a JSON @@ -52,6 +44,11 @@ Linear. ## Notes +!!! note "C++20" + + Since C++20, `basic_json` declares no `operator!=`. The compiler rewrites `#!cpp a != b` as `#!cpp !(a == b)` + using [`operator==`](operator_eq.md), so the result is the same as described above. + !!! note "Comparing `NaN` and `discarded`" Since `operator!=` is defined as `!(a == b)`, the behavior for special values follows that of `operator==`: @@ -61,7 +58,7 @@ Linear. ## Examples -??? example +??? example "Example: (1) compare JSON values" The example demonstrates comparing several JSON types. @@ -75,7 +72,7 @@ Linear. --8<-- "examples/operator__notequal.output" ``` -??? example +??? example "Example: (2) compare JSON values with `#!cpp nullptr`" The example demonstrates comparing several JSON types against the null pointer (JSON `#!json null`). @@ -89,9 +86,15 @@ Linear. --8<-- "examples/operator__notequal__nullptr_t.output" ``` +## See also + +- [operator==](operator_eq.md) comparison: equal +- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) + ## Version history -1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove - special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`. -2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. Changed in version 3.13.0 to remove - special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`. +1. Added in version 1.0.0. Added a C++20 member function in version 3.11.0. Changed in version 3.13.0 to remove + special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`. Removed the C++20 + member function in version 3.13.0; since C++20, the compiler rewrites `a != b` using `operator==`. +2. Added in version 1.0.0. Changed in version 3.13.0 to remove special-casing for `NaN` and `discarded` values; + `operator!=` now consistently means `!(a == b)`. Since C++20, the compiler rewrites `a != b` using `operator==`. diff --git a/docs/mkdocs/docs/api/basic_json/operator_value_t.md b/docs/mkdocs/docs/api/basic_json/operator_value_t.md index 0f08f42b0..04915d935 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_value_t.md +++ b/docs/mkdocs/docs/api/basic_json/operator_value_t.md @@ -47,6 +47,11 @@ Constant. --8<-- "examples/operator__value_t.output" ``` +## See also + +- [type](type.md) named member function equivalent to this implicit conversion operator +- [value_t](value_t.md) the enumeration of JSON types + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/parse.md b/docs/mkdocs/docs/api/basic_json/parse.md index 554065717..c64f93a87 100644 --- a/docs/mkdocs/docs/api/basic_json/parse.md +++ b/docs/mkdocs/docs/api/basic_json/parse.md @@ -114,7 +114,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by ## Examples -??? example "Parsing from a character array" +??? example "Example: (1) parse from a character array" The example below demonstrates the `parse()` function reading from an array. @@ -128,7 +128,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/parse__array__parser_callback_t.output" ``` -??? example "Parsing from a string" +??? example "Example: (1) parse from a string" The example below demonstrates the `parse()` function with and without callback function. @@ -142,7 +142,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/parse__string__parser_callback_t.output" ``` -??? example "Parsing from an input stream" +??? example "Example: (1) parse from an input stream" The example below demonstrates the `parse()` function with and without callback function. @@ -156,7 +156,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/parse__istream__parser_callback_t.output" ``` -??? example "Parsing from a contiguous container" +??? example "Example: (1) parse from a contiguous container" The example below demonstrates the `parse()` function reading from a contiguous container. @@ -170,7 +170,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/parse__contiguouscontainer__parser_callback_t.output" ``` -??? example "Parsing from a non-null-terminated string" +??? example "Example: (2) parse from a non-null-terminated string" The example below demonstrates the `parse()` function reading from a string that is not null-terminated. @@ -184,7 +184,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/parse__pointers.output" ``` -??? example "Parsing from an iterator pair" +??? example "Example: (2) parse from an iterator pair" The example below demonstrates the `parse()` function reading from an iterator pair. @@ -198,7 +198,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/parse__iterator_pair.output" ``` -??? example "Effect of `allow_exceptions` parameter" +??? example "Example: effect of `allow_exceptions` parameter" The example below demonstrates the effect of the `allow_exceptions` parameter in the `parse()` function. @@ -212,7 +212,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/parse__allow_exceptions.output" ``` -??? example "Effect of `ignore_comments` parameter" +??? example "Example: effect of `ignore_comments` parameter" The example below demonstrates the effect of the `ignore_comments` parameter in the `parse()` function. @@ -226,7 +226,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by --8<-- "examples/comments.output" ``` -??? example "Effect of `ignore_trailing_commas` parameter" +??? example "Example: effect of `ignore_trailing_commas` parameter" The example below demonstrates the effect of the `ignore_trailing_commas` parameter in the `parse()` function. @@ -270,3 +270,5 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated function. + + See the [migration guide](../../integration/migration_guide.md#parsing) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/parse_event_t.md b/docs/mkdocs/docs/api/basic_json/parse_event_t.md index 36cba925f..f8c12eff8 100644 --- a/docs/mkdocs/docs/api/basic_json/parse_event_t.md +++ b/docs/mkdocs/docs/api/basic_json/parse_event_t.md @@ -24,6 +24,21 @@ The parser callback distinguishes the following events: ![Example when certain parse events are triggered](../../images/callback_events.png) +??? example + + The following code parses a small JSON text with a parser callback that reports every event together with its + depth and keeps every value (by always returning `#!cpp true`). + + ```cpp + --8<-- "examples/parse_event_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/parse_event_t.output" + ``` + ## See also - [parser_callback_t](parser_callback_t.md) callback function type for the parser diff --git a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md index c1a35afb9..727905e5a 100644 --- a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md +++ b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md @@ -60,7 +60,7 @@ the latter case, it is skipped completely, or replaced by `null` if it is the to ## Examples -??? example +??? example "Example: skip an object key while parsing" The example below demonstrates the `parse()` function with and without callback function. @@ -75,7 +75,7 @@ the latter case, it is skipped completely, or replaced by `null` if it is the to --8<-- "examples/parse__string__parser_callback_t.output" ``` -??? example +??? example "Example: how discarded values are removed" The example below shows where discarded values are removed. The array and the number are discarded in different ways, but in each case the parse result contains neither the value nor its key. diff --git a/docs/mkdocs/docs/api/basic_json/patch.md b/docs/mkdocs/docs/api/basic_json/patch.md index fa25b2699..8c2d7f231 100644 --- a/docs/mkdocs/docs/api/basic_json/patch.md +++ b/docs/mkdocs/docs/api/basic_json/patch.md @@ -26,10 +26,24 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`parse_error.104`](../../home/exceptions.md#jsonexceptionparse_error104) if the JSON patch does not consist of an array of objects. - Throws [`parse_error.105`](../../home/exceptions.md#jsonexceptionparse_error105) if the JSON patch is malformed (e.g., - mandatory attributes are missing); example: `"operation add must have member path"`. + mandatory attributes are missing); example: `"operation 'add' must have member 'path'"`. - Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index is out of range. +- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in a "path" or + "from" member begins with '0'; example: `"array index '01' must not begin with '0'"`. +- Throws [`parse_error.107`](../../home/exceptions.md#jsonexceptionparse_error107) if a "path" or "from" member is not + empty and does not begin with a slash (`/`); example: `"JSON pointer must be empty or begin with '/' - was: 'a'"`. +- Throws [`parse_error.108`](../../home/exceptions.md#jsonexceptionparse_error108) if a tilde (`~`) in a "path" or + "from" member is not followed by `0` or `1`; example: `"escape character '~' must be followed with '0' or '1'"`. +- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a "path" or + "from" member is not a number; example: `"array index 'foo' is not a number"`. +- Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if the array index `-` is used + where an existing element is required (the "path" of "replace", the "from" of "move" and "copy"); example: + `"array index '-' (3) is out of range"`. - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a JSON pointer inside the patch could not be resolved successfully in the current JSON value; example: `"key baz not found"`. +- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token of a JSON + pointer inside the patch cannot be resolved, e.g., `-` in a "remove" operation or `1a` for an array; example: + `"unresolved reference token '-'"`. - Throws [`out_of_range.405`](../../home/exceptions.md#jsonexceptionout_of_range405) if JSON pointer has no parent ("add", "remove", "move") - Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target @@ -53,7 +67,7 @@ is thrown. In any case, the original value is not changed: the patch is applied ## Examples -??? example +??? example "Example: apply a JSON patch" The following code shows how a JSON patch is applied to a value. @@ -67,6 +81,21 @@ is thrown. In any case, the original value is not changed: the patch is applied --8<-- "examples/patch.output" ``` +??? example "Example: out_of_range.414 exception" + + The following code shows how a "move" operation whose "from" location is a proper prefix of its "path" location is + rejected, and how the original document is left unchanged because the patch is applied to a copy. + + ```cpp + --8<-- "examples/patch__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/patch__exception.output" + ``` + ## See also - [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 7ae85aaaa..2d46a2cc8 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -22,10 +22,24 @@ No guarantees, value may be corrupted by an unsuccessful patch operation. - Throws [`parse_error.104`](../../home/exceptions.md#jsonexceptionparse_error104) if the JSON patch does not consist of an array of objects. - Throws [`parse_error.105`](../../home/exceptions.md#jsonexceptionparse_error105) if the JSON patch is malformed (e.g., - mandatory attributes are missing); example: `"operation add must have member path"`. + mandatory attributes are missing); example: `"operation 'add' must have member 'path'"`. - Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index is out of range. +- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in a "path" or + "from" member begins with '0'; example: `"array index '01' must not begin with '0'"`. +- Throws [`parse_error.107`](../../home/exceptions.md#jsonexceptionparse_error107) if a "path" or "from" member is not + empty and does not begin with a slash (`/`); example: `"JSON pointer must be empty or begin with '/' - was: 'a'"`. +- Throws [`parse_error.108`](../../home/exceptions.md#jsonexceptionparse_error108) if a tilde (`~`) in a "path" or + "from" member is not followed by `0` or `1`; example: `"escape character '~' must be followed with '0' or '1'"`. +- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a "path" or + "from" member is not a number; example: `"array index 'foo' is not a number"`. +- Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if the array index `-` is used + where an existing element is required (the "path" of "replace", the "from" of "move" and "copy"); example: + `"array index '-' (3) is out of range"`. - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a JSON pointer inside the patch could not be resolved successfully in the current JSON value; example: `"key baz not found"`. +- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token of a JSON + pointer inside the patch cannot be resolved, e.g., `-` in a "remove" operation or `1a` for an array; example: + `"unresolved reference token '-'"`. - Throws [`out_of_range.405`](../../home/exceptions.md#jsonexceptionout_of_range405) if JSON pointer has no parent ("add", "remove", "move") - Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target @@ -50,7 +64,7 @@ function throws an exception. ## Examples -??? example +??? example "Example: apply a JSON patch in place" The following code shows how a JSON patch is applied to a value. @@ -64,6 +78,22 @@ function throws an exception. --8<-- "examples/patch_inplace.output" ``` +??? example "Example: out_of_range.403 exception with a partially applied patch" + + The following code shows a patch whose first operation succeeds and whose second operation fails. Because + `patch_inplace` applies each operation directly to the value, the first operation's effect is still visible after + the exception is caught, unlike [`patch`](patch.md). + + ```cpp + --8<-- "examples/patch_inplace__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/patch_inplace__exception.output" + ``` + ## See also - [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) diff --git a/docs/mkdocs/docs/api/basic_json/push_back.md b/docs/mkdocs/docs/api/basic_json/push_back.md index 9d518e79e..386a589a3 100644 --- a/docs/mkdocs/docs/api/basic_json/push_back.md +++ b/docs/mkdocs/docs/api/basic_json/push_back.md @@ -44,6 +44,12 @@ invalidates all iterators and all references. `init` (in) : an initializer list +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes to any JSON value. As an exception, a `#!json null` +value is converted to an empty array or object before the element is added and keeps that type if adding the element +throws. + ## Exceptions 1. Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) when called on a type other than diff --git a/docs/mkdocs/docs/api/basic_json/rbegin.md b/docs/mkdocs/docs/api/basic_json/rbegin.md index 5fbbe7d3b..544b8a643 100644 --- a/docs/mkdocs/docs/api/basic_json/rbegin.md +++ b/docs/mkdocs/docs/api/basic_json/rbegin.md @@ -37,6 +37,13 @@ Constant. --8<-- "examples/rbegin.output" ``` +## See also + +- [rend](rend.md) returns a reverse iterator to one before the first element +- [crbegin](crbegin.md) returns a const reverse iterator to the last element +- [begin](begin.md) returns an iterator to the first element +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/rend.md b/docs/mkdocs/docs/api/basic_json/rend.md index 8eb1a6e55..12f68e787 100644 --- a/docs/mkdocs/docs/api/basic_json/rend.md +++ b/docs/mkdocs/docs/api/basic_json/rend.md @@ -38,6 +38,13 @@ Constant. --8<-- "examples/rend.output" ``` +## See also + +- [rbegin](rbegin.md) returns a reverse iterator to the last element +- [crend](crend.md) returns a const reverse iterator to one before the first element +- [end](end.md) returns an iterator to one past the last element +- [Iterators](../../features/iterators.md) - the article on iterators + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/sax_parse.md b/docs/mkdocs/docs/api/basic_json/sax_parse.md index d6ce688a7..0d4b8da8a 100644 --- a/docs/mkdocs/docs/api/basic_json/sax_parse.md +++ b/docs/mkdocs/docs/api/basic_json/sax_parse.md @@ -147,3 +147,5 @@ A UTF-8 byte order mark is silently ignored. Overload (2) replaces calls to `sax_parse` with a pair of iterators as their first parameter which has been deprecated in version 3.8.0. This overload will be removed in version 4.0.0. Please replace all calls like `#!cpp sax_parse({ptr, ptr+len});` with `#!cpp sax_parse(ptr, ptr+len);`. + + See the [migration guide](../../integration/migration_guide.md#parsing) for how to update existing code. diff --git a/docs/mkdocs/docs/api/basic_json/size.md b/docs/mkdocs/docs/api/basic_json/size.md index 4ff582db2..c3d9156ba 100644 --- a/docs/mkdocs/docs/api/basic_json/size.md +++ b/docs/mkdocs/docs/api/basic_json/size.md @@ -51,6 +51,11 @@ JSON value which is `1` in the case of a string. --8<-- "examples/size.output" ``` +## See also + +- [empty](empty.md) checks whether the JSON value has no elements +- [max_size](max_size.md) returns the maximum possible number of elements + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/std_hash.md b/docs/mkdocs/docs/api/basic_json/std_hash.md index b9de74f8c..aaa49ed68 100644 --- a/docs/mkdocs/docs/api/basic_json/std_hash.md +++ b/docs/mkdocs/docs/api/basic_json/std_hash.md @@ -28,6 +28,10 @@ type of the JSON value is taken into account to have different hash values for ` Note the output is platform-dependent. +## See also + +- [operator==](operator_eq.md) compares two JSON values for equality, consistent with equal hash values + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/string_t.md b/docs/mkdocs/docs/api/basic_json/string_t.md index e8be0fe0e..13177cef8 100644 --- a/docs/mkdocs/docs/api/basic_json/string_t.md +++ b/docs/mkdocs/docs/api/basic_json/string_t.md @@ -30,16 +30,16 @@ JSON class into byte-sized characters during deserialization. ## Notes -#### Default type +### Default type With the default values for `StringType` (`std::string`), the default value for `string_t` is `#!cpp std::string`. -#### Encoding +### Encoding Strings are stored in UTF-8 encoding. Therefore, functions like `std::string::size()` or `std::string::length()` return the number of bytes in the string rather than the number of characters or glyphs. -#### String comparison +### String comparison [RFC 8259](https://tools.ietf.org/html/rfc8259) states: > Software implementations are typically required to test names of object members for equality. Implementations that @@ -50,15 +50,15 @@ the number of bytes in the string rather than the number of characters or glyphs This implementation is interoperable as it does compare strings code unit by code unit. -#### Storage +### Storage String values are stored as pointers in a `basic_json` type. That is, for any access to string values, a pointer of type `string_t*` must be dereferenced. -#### Cross-`basic_json` conversion requirements +### Cross-`basic_json` conversion requirements When converting a string value from one `basic_json` specialization to another via the -[converting constructor](basic_json.md#overload-4), the target `string_t` must be directly +[converting constructor](basic_json.md) (overload 4), the target `string_t` must be directly constructible from the source `basic_json`'s `string_t` type. If this requirement is not met, the conversion does not fail; instead, the string is silently converted as an array of character codes, which is incorrect. See [issue #3425](https://github.com/nlohmann/json/issues/3425) for details @@ -80,6 +80,12 @@ and an example. --8<-- "examples/string_t.output" ``` +## See also + +- [object_t](object_t.md) the type used to store JSON objects (and their keys, which are also `string_t`) +- [binary_t](binary_t.md) the type used to store binary values +- [get_ptr](get_ptr.md) returns a pointer to the stored string value + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/swap.md b/docs/mkdocs/docs/api/basic_json/swap.md index 3ac74288d..4704852f3 100644 --- a/docs/mkdocs/docs/api/basic_json/swap.md +++ b/docs/mkdocs/docs/api/basic_json/swap.md @@ -73,6 +73,16 @@ void swap(typename binary_t::container_type& other); `right` (in, out) : value to exchange the contents with +## Exception safety + +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee: this function never throws exceptions. +3. Strong guarantee: if an exception is thrown, there are no changes to any JSON value. +4. Strong guarantee: if an exception is thrown, there are no changes to any JSON value. +5. Strong guarantee: if an exception is thrown, there are no changes to any JSON value. +6. Strong guarantee: if an exception is thrown, there are no changes to any JSON value. +7. Strong guarantee: if an exception is thrown, there are no changes to any JSON value. + ## Exceptions 1. No-throw guarantee: this function never throws exceptions. @@ -94,7 +104,7 @@ Constant. ## Examples -??? example "Example: Swap JSON value (1, 2)" +??? example "Example: (1, 2) swap JSON values" The example below shows how JSON values can be swapped with `swap()`. @@ -108,7 +118,7 @@ Constant. --8<-- "examples/swap__reference.output" ``` -??? example "Example: Swap array (3)" +??? example "Example: (3) swap array" The example below shows how arrays can be swapped with `swap()`. @@ -122,7 +132,7 @@ Constant. --8<-- "examples/swap__array_t.output" ``` -??? example "Example: Swap object (4)" +??? example "Example: (4) swap object" The example below shows how objects can be swapped with `swap()`. @@ -136,7 +146,7 @@ Constant. --8<-- "examples/swap__object_t.output" ``` -??? example "Example: Swap string (5)" +??? example "Example: (5) swap string" The example below shows how strings can be swapped with `swap()`. @@ -150,7 +160,7 @@ Constant. --8<-- "examples/swap__string_t.output" ``` -??? example "Example: Swap binary (6)" +??? example "Example: (6) swap binary" The example below shows how binary values can be swapped with `swap()`. diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index b066e6852..44cc399e1 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -55,7 +55,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va ## Exceptions - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` - is false. + is false, and `j` contains a non-empty array, object, or binary value. ## Complexity @@ -63,7 +63,7 @@ Linear in the size of the JSON value `j`. ## Examples -??? example +??? example "Example: serialize a JSON value to BJData" The example shows the serialization of a JSON value to a byte vector in BJData format. @@ -77,6 +77,21 @@ Linear in the size of the JSON value `j`. --8<-- "examples/to_bjdata.output" ``` +??? example "Example: other_error.502 exception" + + The example shows how requesting type annotations (`use_type`) without size annotations (`use_size`) throws an + exception, because type-optimized containers can only be read back with a preceding size. + + ```cpp + --8<-- "examples/to_bjdata__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/to_bjdata__exception.output" + ``` + ## See also - [from_bjdata](from_bjdata.md) create a JSON value from an input in BJData format diff --git a/docs/mkdocs/docs/api/basic_json/to_bon8.md b/docs/mkdocs/docs/api/basic_json/to_bon8.md index 26d552e8a..2d58b2660 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/to_bon8.md @@ -48,7 +48,7 @@ Linear in the size of the JSON value `j`. ## Examples -??? example +??? example "Example: serialize a JSON value to BON8" The example shows the serialization of a JSON value to a byte vector in BON8 format. @@ -62,6 +62,21 @@ Linear in the size of the JSON value `j`. --8<-- "examples/to_bon8.output" ``` +??? example "Example: type_error.316 exception" + + The example shows how serializing a string that is not valid UTF-8 throws an exception, because BON8 stores strings + as UTF-8. + + ```cpp + --8<-- "examples/to_bon8__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/to_bon8__exception.output" + ``` + ## See also - [from_bon8](from_bon8.md) create a JSON value from an input in BON8 format diff --git a/docs/mkdocs/docs/api/basic_json/to_bson.md b/docs/mkdocs/docs/api/basic_json/to_bson.md index 5ba3c8bb4..c79f39ffc 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bson.md +++ b/docs/mkdocs/docs/api/basic_json/to_bson.md @@ -54,7 +54,7 @@ pass before anything is written. ## Examples -??? example +??? example "Example: serialize a JSON value to BSON" The example shows the serialization of a JSON value to a byte vector in BSON format. @@ -68,6 +68,21 @@ pass before anything is written. --8<-- "examples/to_bson.output" ``` +??? example "Example: out_of_range.409 exception" + + The example shows how serializing a JSON object whose key contains a null byte (U+0000) throws an exception, because + BSON keys are null-terminated C strings and cannot contain U+0000 themselves. + + ```cpp + --8<-- "examples/to_bson__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/to_bson__exception.output" + ``` + ## See also - [from_bson](from_bson.md) create a JSON value from an input in BSON format @@ -80,5 +95,6 @@ pass before anything is written. ## Version history - Added in version 3.4.0. +- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. - Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0. - `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/to_msgpack.md b/docs/mkdocs/docs/api/basic_json/to_msgpack.md index 19f86f9c2..707de9514 100644 --- a/docs/mkdocs/docs/api/basic_json/to_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/to_msgpack.md @@ -49,7 +49,7 @@ Linear in the size of the JSON value `j`. ## Examples -??? example +??? example "Example: serialize a JSON value to MessagePack" The example shows the serialization of a JSON value to a byte vector in MessagePack format. @@ -63,6 +63,21 @@ Linear in the size of the JSON value `j`. --8<-- "examples/to_msgpack.output" ``` +??? example "Example: out_of_range.415 exception" + + The example shows how serializing a binary value whose subtype exceeds 255 throws an exception, because the + MessagePack ext type stores the subtype in a single byte. + + ```cpp + --8<-- "examples/to_msgpack__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/to_msgpack__exception.output" + ``` + ## See also - [from_msgpack](from_msgpack.md) create a JSON value from an input in MessagePack format diff --git a/docs/mkdocs/docs/api/basic_json/to_string.md b/docs/mkdocs/docs/api/basic_json/to_string.md index 2df16c132..8c75db84c 100644 --- a/docs/mkdocs/docs/api/basic_json/to_string.md +++ b/docs/mkdocs/docs/api/basic_json/to_string.md @@ -10,7 +10,8 @@ This function implements a user-defined to_string for JSON objects. ## Template parameters `BasicJsonType` -: a specialization of [`basic_json`](index.md) +: a specialization of [`basic_json`](index.md) whose [`string_t`](string_t.md) is convertible to `#!cpp std::string`; + for other string types, use [`dump`](dump.md), which returns a `string_t` ## Return value diff --git a/docs/mkdocs/docs/api/basic_json/to_ubjson.md b/docs/mkdocs/docs/api/basic_json/to_ubjson.md index 6437ba3e5..1b7f7767e 100644 --- a/docs/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/to_ubjson.md @@ -48,7 +48,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va ## Exceptions - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` - is false. + is false, and `j` contains a non-empty array, object, or binary value. ## Complexity @@ -56,7 +56,7 @@ Linear in the size of the JSON value `j`. ## Examples -??? example +??? example "Example: serialize a JSON value to UBJSON" The example shows the serialization of a JSON value to a byte vector in UBJSON format. @@ -70,6 +70,21 @@ Linear in the size of the JSON value `j`. --8<-- "examples/to_ubjson.output" ``` +??? example "Example: other_error.502 exception" + + The example shows how requesting type annotations (`use_type`) without size annotations (`use_size`) throws an + exception, because type-optimized containers can only be read back with a preceding size. + + ```cpp + --8<-- "examples/to_ubjson__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/to_ubjson__exception.output" + ``` + ## See also - [from_ubjson](from_ubjson.md) create a JSON value from an input in UBJSON format diff --git a/docs/mkdocs/docs/api/basic_json/type.md b/docs/mkdocs/docs/api/basic_json/type.md index deedd6b69..381e489d2 100644 --- a/docs/mkdocs/docs/api/basic_json/type.md +++ b/docs/mkdocs/docs/api/basic_json/type.md @@ -47,6 +47,12 @@ Constant. --8<-- "examples/type.output" ``` +## See also + +- [operator value_t](operator_value_t.md) implicit conversion operator equivalent to this named member function +- [type_name](type_name.md) returns the type as a string, for use in error messages +- [value_t](value_t.md) the enumeration of JSON types + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/type_name.md b/docs/mkdocs/docs/api/basic_json/type_name.md index 2022b8897..0df2de752 100644 --- a/docs/mkdocs/docs/api/basic_json/type_name.md +++ b/docs/mkdocs/docs/api/basic_json/type_name.md @@ -52,6 +52,11 @@ Constant. --8<-- "examples/type_name.output" ``` +## See also + +- [type](type.md) returns the type of the JSON value +- [value_t](value_t.md) the enumeration of JSON types + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/unflatten.md b/docs/mkdocs/docs/api/basic_json/unflatten.md index ac88cd73e..a8f0a60b0 100644 --- a/docs/mkdocs/docs/api/basic_json/unflatten.md +++ b/docs/mkdocs/docs/api/basic_json/unflatten.md @@ -27,8 +27,17 @@ The function can throw the following exceptions: - Throws [`type_error.315`](../../home/exceptions.md#jsonexceptiontype_error315) if object values are not primitive - Throws [`type_error.313`](../../home/exceptions.md#jsonexceptiontype_error313) if a key (JSON pointer) leads to a conflicting nesting; example: `"invalid value to unflatten"` +- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in a key begins + with '0'; example: `"array index '01' must not begin with '0'"` +- Throws [`parse_error.107`](../../home/exceptions.md#jsonexceptionparse_error107) if a key is not empty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or begin with '/' - was: 'a'"` +- Throws [`parse_error.108`](../../home/exceptions.md#jsonexceptionparse_error108) if a tilde (`~`) in a key is not + followed by `0` or `1`; example: `"escape character '~' must be followed with '0' or '1'"` - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a key is not a number; example: `"array index 'one' is not a number"` +- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a level becomes an array + (because one of its keys is `0`) and another key at that level cannot be an array index; example: + `"unresolved reference token 'x'"` ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json/update.md b/docs/mkdocs/docs/api/basic_json/update.md index db8bfb745..d641b38d5 100644 --- a/docs/mkdocs/docs/api/basic_json/update.md +++ b/docs/mkdocs/docs/api/basic_json/update.md @@ -67,7 +67,7 @@ contained in `#!cpp *this` (for example, a subobject returned by `#!cpp (*this)[ ## Examples -??? example +??? example "Example: (1) update with another object" The example shows how `update()` is used. @@ -81,7 +81,7 @@ contained in `#!cpp *this` (for example, a subobject returned by `#!cpp (*this)[ --8<-- "examples/update.output" ``` -??? example +??? example "Example: (2) update with an iterator range" The example shows how `update()` is used. @@ -95,7 +95,7 @@ contained in `#!cpp *this` (for example, a subobject returned by `#!cpp (*this)[ --8<-- "examples/update__range.output" ``` -??? example +??? example "Example: (1) merge user settings into default settings" One common use case for this function is the handling of user settings. Assume your application can be configured in some aspects: diff --git a/docs/mkdocs/docs/api/basic_json/value.md b/docs/mkdocs/docs/api/basic_json/value.md index c0e4b9949..56f4bcc0f 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -141,6 +141,18 @@ changes to any JSON value. --8<-- "examples/value__return_type.output" ``` +!!! warning "Deprecation" + + Overload (3) also accepts a [`json_pointer`](../json_pointer/index.md) whose template argument is a `basic_json` + specialization (e.g., `nlohmann::json_pointer`) instead of a string type. This is deprecated since + version 3.11.0 and will be removed in a future major version; use `basic_json::json_pointer` (for `json`, + `nlohmann::json_pointer`) instead. + + You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated + function. + + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + ## Examples ??? example "Example: (1) access specified object element with default value" @@ -185,6 +197,21 @@ changes to any JSON value. --8<-- "examples/value__json_ptr.output" ``` +??? example "Example: (1) type_error.302 and type_error.306 exceptions" + + The example below shows how `value()` throws `type_error.302` when the default value's type does not match the type + of the stored value, and `type_error.306` when `value()` is called on a JSON value that is not an object. + + ```cpp + --8<-- "examples/value__exception.cpp" + ``` + + Output: + + ```json + --8<-- "examples/value__exception.output" + ``` + ## See also - see [`at`](at.md) for access by reference with range checking diff --git a/docs/mkdocs/docs/api/basic_json/value_t.md b/docs/mkdocs/docs/api/basic_json/value_t.md index 1505e02d0..0d63bfdac 100644 --- a/docs/mkdocs/docs/api/basic_json/value_t.md +++ b/docs/mkdocs/docs/api/basic_json/value_t.md @@ -38,6 +38,16 @@ functions [`is_null`](is_null.md), [`is_object`](is_object.md), [`is_array`](is_ `discarded` is unordered. + ```mermaid + flowchart LR + A[null] --> B[boolean] + B --> C["number_integer / number_unsigned / number_float"] + C --> D[object] + D --> E[array] + E --> F[string] + F --> G[binary] + ``` + !!! note "Types of numbers" There are three enumerators for numbers (`number_integer`, `number_unsigned`, and `number_float`) to distinguish @@ -74,6 +84,14 @@ functions [`is_null`](is_null.md), [`is_object`](is_object.md), [`is_array`](is_ --8<-- "examples/type.output" ``` +## See also + +- [type](type.md) return the type of the JSON value +- [type_name](type_name.md) return the type as string +- [operator value_t](operator_value_t.md) return the type of the JSON value +- [is_primitive](is_primitive.md) return whether the type is primitive +- [is_structured](is_structured.md) return whether the type is structured + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/~basic_json.md b/docs/mkdocs/docs/api/basic_json/~basic_json.md index 64e944006..e5b957d51 100644 --- a/docs/mkdocs/docs/api/basic_json/~basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/~basic_json.md @@ -16,6 +16,11 @@ Linear. +## See also + +- [basic_json](basic_json.md) constructs a JSON value +- [clear](clear.md) clears the content of a JSON value without destroying it + ## Version history - Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md index c8e47cfa3..23d5c9beb 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md @@ -41,6 +41,14 @@ byte_container_with_subtype(container_type&& container, subtype_type subtype); --8<-- "examples/byte_container_with_subtype__byte_container_with_subtype.output" ``` +## See also + +- [set_subtype](set_subtype.md) sets the binary subtype +- [subtype](subtype.md) return the binary subtype +- [has_subtype](has_subtype.md) return whether the value has a subtype +- [binary](../basic_json/binary.md) create a binary JSON value +- [Binary Values](../../features/binary_values.md) - the article on binary values + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md index f4bb891ee..7fd8fe9a4 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md @@ -31,6 +31,12 @@ Constant. --8<-- "examples/byte_container_with_subtype__clear_subtype.output" ``` +## See also + +- [set_subtype](set_subtype.md) sets the binary subtype +- [has_subtype](has_subtype.md) return whether the value has a subtype +- [subtype](subtype.md) return the binary subtype + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md index e06286e29..98596dab0 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md @@ -34,6 +34,12 @@ Constant. --8<-- "examples/byte_container_with_subtype__has_subtype.output" ``` +## See also + +- [set_subtype](set_subtype.md) sets the binary subtype +- [clear_subtype](clear_subtype.md) clears the binary subtype +- [subtype](subtype.md) return the binary subtype + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/index.md b/docs/mkdocs/docs/api/byte_container_with_subtype/index.md index a9aafc1d2..cb5a3781d 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/index.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/index.md @@ -22,8 +22,8 @@ specific naming scheme in order to override the binary type. ## Member functions - [(constructor)](byte_container_with_subtype.md) -- **operator==** - comparison: equal -- **operator!=** - comparison: not equal +- [**operator==**](operator_eq.md) - comparison: equal +- [**operator!=**](operator_ne.md) - comparison: not equal - [**set_subtype**](set_subtype.md) - sets the binary subtype - [**subtype**](subtype.md) - return the binary subtype - [**has_subtype**](has_subtype.md) - return whether the value has a subtype diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md new file mode 100644 index 000000000..0676ee5e7 --- /dev/null +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md @@ -0,0 +1,48 @@ +# nlohmann::byte_container_with_subtype::operator== + +```cpp +bool operator==(const byte_container_with_subtype& rhs) const; +``` + +Compares two byte containers for equality by comparing (1) the underlying binary data (the `BinaryType` base, compared +with `BinaryType`'s own `operator==`) and (2) the subtype information -- both containers must either have no subtype, +or have a subtype and the same subtype value. + +## Parameters + +`rhs` (in) +: byte container to compare `*this` with + +## Return value + +whether `*this` and `rhs` are equal + +## Complexity + +Linear in the size of the compared containers. + +## Examples + +??? example + + The example below demonstrates comparing byte containers with and without subtypes. + + ```cpp + --8<-- "examples/byte_container_with_subtype__operator__equal.cpp" + ``` + + Output: + + ```json + --8<-- "examples/byte_container_with_subtype__operator__equal.output" + ``` + +## See also + +- [operator!=](operator_ne.md) comparison: not equal +- [has_subtype](has_subtype.md) return whether the value has a subtype +- [subtype](subtype.md) return the binary subtype + +## Version history + +- Added in version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md new file mode 100644 index 000000000..5adb5cd39 --- /dev/null +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md @@ -0,0 +1,47 @@ +# nlohmann::byte_container_with_subtype::operator!= + +```cpp +bool operator!=(const byte_container_with_subtype& rhs) const; +``` + +Compares two byte containers for inequality. Returns `#!cpp !(rhs == *this)`; see [`operator==`](operator_eq.md) for +the equality semantics. + +## Parameters + +`rhs` (in) +: byte container to compare `*this` with + +## Return value + +whether `*this` and `rhs` are not equal + +## Complexity + +Linear in the size of the compared containers. + +## Examples + +??? example + + The example below demonstrates comparing byte containers with and without subtypes. + + ```cpp + --8<-- "examples/byte_container_with_subtype__operator__notequal.cpp" + ``` + + Output: + + ```json + --8<-- "examples/byte_container_with_subtype__operator__notequal.output" + ``` + +## See also + +- [operator==](operator_eq.md) comparison: equal +- [has_subtype](has_subtype.md) return whether the value has a subtype +- [subtype](subtype.md) return the binary subtype + +## Version history + +- Added in version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md index cf21732b8..6cd319e1d 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md @@ -36,6 +36,12 @@ Constant. --8<-- "examples/byte_container_with_subtype__set_subtype.output" ``` +## See also + +- [subtype](subtype.md) return the binary subtype +- [has_subtype](has_subtype.md) return whether the value has a subtype +- [clear_subtype](clear_subtype.md) clears the binary subtype + ## Version history Since version 3.8.0. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md index 389241a79..0f00cc803 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md @@ -36,6 +36,12 @@ Constant. --8<-- "examples/byte_container_with_subtype__subtype.output" ``` +## See also + +- [set_subtype](set_subtype.md) sets the binary subtype +- [has_subtype](has_subtype.md) return whether the value has a subtype +- [clear_subtype](clear_subtype.md) clears the binary subtype + ## Version history - Added in version 3.8.0 diff --git a/docs/mkdocs/docs/api/json.md b/docs/mkdocs/docs/api/json.md index 36edcc2c1..55504ef9a 100644 --- a/docs/mkdocs/docs/api/json.md +++ b/docs/mkdocs/docs/api/json.md @@ -23,6 +23,11 @@ types. --8<-- "examples/README.output" ``` +## See also + +- [basic_json](basic_json/index.md) - the underlying class template +- [ordered_json](ordered_json.md) - specialization that preserves the insertion order of object keys + ## Version history Since version 1.0.0. diff --git a/docs/mkdocs/docs/api/json_pointer/back.md b/docs/mkdocs/docs/api/json_pointer/back.md index 7b798e368..b8f0432af 100644 --- a/docs/mkdocs/docs/api/json_pointer/back.md +++ b/docs/mkdocs/docs/api/json_pointer/back.md @@ -10,6 +10,10 @@ Return the last reference token. Last reference token. +## Exception safety + +Strong exception safety: if an exception occurs, the original value stays intact. + ## Exceptions Throws [out_of_range.405](../../home/exceptions.md#jsonexceptionout_of_range405) if the JSON pointer has no parent. @@ -34,6 +38,13 @@ Constant. --8<-- "examples/json_pointer__back.output" ``` +## See also + +- [front](front.md) return first reference token +- [pop_back](pop_back.md) remove the last reference token +- [push_back](push_back.md) append an unescaped token at the end of the pointer +- [parent_pointer](parent_pointer.md) returns the parent of this JSON pointer + ## Version history - Added in version 3.6.0. diff --git a/docs/mkdocs/docs/api/json_pointer/empty.md b/docs/mkdocs/docs/api/json_pointer/empty.md index 96328bd23..6c8fd542e 100644 --- a/docs/mkdocs/docs/api/json_pointer/empty.md +++ b/docs/mkdocs/docs/api/json_pointer/empty.md @@ -34,6 +34,12 @@ Constant. --8<-- "examples/json_pointer__empty.output" ``` +## See also + +- [front](front.md) return first reference token +- [back](back.md) return last reference token +- [to_string](to_string.md) return a string representation of the JSON pointer + ## Version history Added in version 3.6.0. diff --git a/docs/mkdocs/docs/api/json_pointer/front.md b/docs/mkdocs/docs/api/json_pointer/front.md index afc6f5ca7..247abd188 100644 --- a/docs/mkdocs/docs/api/json_pointer/front.md +++ b/docs/mkdocs/docs/api/json_pointer/front.md @@ -10,6 +10,10 @@ Return the first reference token. First reference token. +## Exception safety + +Strong exception safety: if an exception occurs, the original value stays intact. + ## Exceptions Throws [out_of_range.405](../../home/exceptions.md#jsonexceptionout_of_range405) if the JSON pointer has no parent. @@ -34,6 +38,12 @@ Constant. --8<-- "examples/json_pointer__front.output" ``` +## See also + +- [back](back.md) return last reference token +- [pop_front](pop_front.md) remove the first reference token +- [push_front](push_front.md) append an unescaped token at the start of the pointer + ## Version history - Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/json_pointer/index.md b/docs/mkdocs/docs/api/json_pointer/index.md index b1f895375..c64543c94 100644 --- a/docs/mkdocs/docs/api/json_pointer/index.md +++ b/docs/mkdocs/docs/api/json_pointer/index.md @@ -20,6 +20,24 @@ are the base for JSON patches. in which case `string_t` will be deduced as [`basic_json::string_t`](../basic_json/string_t.md). This feature is deprecated and may be removed in a future major version. + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + +A JSON pointer is internally a sequence of reference tokens. [`front`](front.md), [`pop_front`](pop_front.md), and +[`push_front`](push_front.md) act on the first reference token, whereas [`back`](back.md), [`pop_back`](pop_back.md), +and [`push_back`](push_back.md) act on the last one. [`parent_pointer`](parent_pointer.md) returns a new JSON pointer +with the last reference token removed (like a non-mutating [`pop_back`](pop_back.md)): + +```mermaid +flowchart LR + A["a"] --> B["b"] --> C["c"] + + front["front() / pop_front() / push_front()"] -.-> A + back["back() / pop_back() / push_back()"] -.-> C + parent["parent_pointer() returns /a/b"] -.-> B +``` + +The diagram shows the reference tokens of the JSON pointer `/a/b/c`. + ## Member types - [**string_t**](string_t.md) - the string type used for the reference tokens @@ -28,9 +46,10 @@ are the base for JSON patches. - [(constructor)](json_pointer.md) - [**to_string**](to_string.md) - return a string representation of the JSON pointer -- [**operator string_t**](operator_string_t.md) - return a string representation of the JSON pointer +- [**operator string_t**](operator_string_t.md) - return a string representation of the JSON pointer (deprecated) - [**operator==**](operator_eq.md) - compare: equal - [**operator!=**](operator_ne.md) - compare: not equal +- [**operator<=>**](operator_spaceship.md) - compare: 3-way (C++20) - [**operator/=**](operator_slasheq.md) - append to the end of the JSON pointer - [**operator/**](operator_slash.md) - create JSON Pointer by appending - [**parent_pointer**](parent_pointer.md) - returns the parent of this JSON pointer @@ -45,6 +64,7 @@ are the base for JSON patches. ## Literals - [**operator""_json_pointer**](../operator_literal_json_pointer.md) - user-defined string literal for JSON pointers + ## See also - [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) diff --git a/docs/mkdocs/docs/api/json_pointer/json_pointer.md b/docs/mkdocs/docs/api/json_pointer/json_pointer.md index 5e7057fc9..0fce6f43c 100644 --- a/docs/mkdocs/docs/api/json_pointer/json_pointer.md +++ b/docs/mkdocs/docs/api/json_pointer/json_pointer.md @@ -12,6 +12,10 @@ Create a JSON pointer according to the syntax described in `s` (in) : string representing the JSON pointer; if omitted, the empty string is assumed which references the whole JSON value +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes to any JSON pointer. + ## Exceptions - Throws [parse_error.107](../../home/exceptions.md#jsonexceptionparse_error107) if the given JSON pointer `s` is @@ -19,6 +23,10 @@ Create a JSON pointer according to the syntax described in - Throws [parse_error.108](../../home/exceptions.md#jsonexceptionparse_error108) if a tilde (`~`) in the given JSON pointer `s` is not followed by `0` (representing `~`) or `1` (representing `/`); see example below. +## Complexity + +Linear in the length of `s`. + ## Examples ??? example @@ -35,6 +43,11 @@ Create a JSON pointer according to the syntax described in --8<-- "examples/json_pointer.output" ``` +## See also + +- [JSON Pointer](../../features/json_pointer.md) - the article on JSON Pointer support +- [operator""_json_pointer](../operator_literal_json_pointer.md) user-defined string literal for JSON pointers + ## Version history - Added in version 2.0.0. diff --git a/docs/mkdocs/docs/api/json_pointer/operator_eq.md b/docs/mkdocs/docs/api/json_pointer/operator_eq.md index 807ae1d0c..a9f40891f 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_eq.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_eq.md @@ -77,6 +77,8 @@ tokens. Overload 2 is deprecated and will be removed in a future major version release. + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + ## Examples ??? example "Example: (1) Comparing JSON pointers" @@ -107,6 +109,11 @@ tokens. --8<-- "examples/json_pointer__operator__equal_stringtype.output" ``` +## See also + +- [operator!=](operator_ne.md) compare for inequality +- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) + ## Version history 1. Added in version 2.1.0. Added C++20 member functions in version 3.11.2. diff --git a/docs/mkdocs/docs/api/json_pointer/operator_ne.md b/docs/mkdocs/docs/api/json_pointer/operator_ne.md index 1f3e3247e..77e6b92c8 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_ne.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_ne.md @@ -73,6 +73,8 @@ tokens. Overload 2 is deprecated and will be removed in a future major version release. + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + ## Examples ??? example "Example: (1) Comparing JSON pointers" @@ -103,6 +105,11 @@ tokens. --8<-- "examples/json_pointer__operator__notequal_stringtype.output" ``` +## See also + +- [operator==](operator_eq.md) compare for equality +- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) + ## Version history 1. Added in version 2.1.0. diff --git a/docs/mkdocs/docs/api/json_pointer/operator_slash.md b/docs/mkdocs/docs/api/json_pointer/operator_slash.md index ed77b504b..e91f8a119 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_slash.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_slash.md @@ -35,6 +35,11 @@ json_pointer operator/(const json_pointer& lhs, std::size_t array_idx); 2. a new JSON pointer with unescaped `token` appended to `lhs` 3. a new JSON pointer with `array_idx` appended to `lhs` +## Exception safety + +Strong guarantee: if an exception is thrown, there are no changes to any JSON pointer. The operands are not modified; +a new JSON pointer is built from a copy of `lhs`. + ## Complexity 1. Linear in the length of `lhs` and `rhs`. @@ -57,6 +62,11 @@ json_pointer operator/(const json_pointer& lhs, std::size_t array_idx); --8<-- "examples/json_pointer__operator_add_binary.output" ``` +## See also + +- [operator/=](operator_slasheq.md) append to the end of the JSON pointer +- [push_back](push_back.md) append an unescaped token at the end of the pointer + ## Version history 1. Added in version 3.6.0. diff --git a/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md b/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md index 3518557d5..05e8ccbcf 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md @@ -32,6 +32,13 @@ json_pointer& operator/=(std::size_t array_idx) 2. JSON pointer with `token` appended without escaping `token` 3. JSON pointer with `array_idx` appended +## Exception safety + +1. Basic guarantee: if an exception is thrown (for instance, if copying a reference token fails), the JSON pointer is + left in a valid state, but it may contain some of the reference tokens of `ptr`. +2. Strong guarantee: if an exception is thrown, there are no changes to the JSON pointer. +3. Strong guarantee: if an exception is thrown, there are no changes to the JSON pointer. + ## Complexity 1. Linear in the length of `ptr`. @@ -54,6 +61,11 @@ json_pointer& operator/=(std::size_t array_idx) --8<-- "examples/json_pointer__operator_add.output" ``` +## See also + +- [operator/](operator_slash.md) create JSON Pointer by appending +- [push_back](push_back.md) append an unescaped token at the end of the pointer + ## Version history 1. Added in version 3.6.0. diff --git a/docs/mkdocs/docs/api/json_pointer/operator_spaceship.md b/docs/mkdocs/docs/api/json_pointer/operator_spaceship.md new file mode 100644 index 000000000..2ec917b72 --- /dev/null +++ b/docs/mkdocs/docs/api/json_pointer/operator_spaceship.md @@ -0,0 +1,74 @@ +# nlohmann::json_pointer::operator<=> + +```cpp +// since C++20 +class json_pointer { + template + std::strong_ordering operator<=>(const json_pointer& rhs) const noexcept; // *NOPAD* +}; +``` + +3-way compares two JSON pointers by lexicographically comparing their sequences of reference tokens: corresponding +reference tokens are compared with `string_t`'s own `operator<=>`, and the first pair of tokens that differs +determines the result. If all corresponding reference tokens compare equal, the JSON pointer with fewer reference +tokens is ordered first. + +## Template parameters + +`RefStringTypeRhs` +: the string type of the right-hand side JSON pointer + +## Parameters + +`rhs` (in) +: JSON pointer to compare `*this` with + +## Return value + +the `std::strong_ordering` of the 3-way comparison of `*this` and `rhs` + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Linear in the number of reference tokens. + +## Notes + +!!! note "Ordering enables use as an associative container key" + + Together with [`operator==`](operator_eq.md), `operator<=>` makes `json_pointer` a `LessThanComparable` type, so + it can be used as the key type of ordered associative containers such as `std::map` or `std::set`. + +!!! note "Before C++20" + + Without C++20's three-way comparison, `json_pointer` provides a non-member `operator<` instead, which orders JSON + pointers the same way. JSON pointers can therefore be used as keys of ordered associative containers with any + supported C++ standard. + +## Examples + +??? example + + The example demonstrates 3-way comparing JSON pointers. + + ```cpp + --8<-- "examples/json_pointer__operator_spaceship.c++20.cpp" + ``` + + Output: + + ``` + --8<-- "examples/json_pointer__operator_spaceship.c++20.output" + ``` + +## See also + +- [operator==](operator_eq.md) compare: equal +- [operator!=](operator_ne.md) compare: not equal + +## Version history + +- Added in version 3.11.2. diff --git a/docs/mkdocs/docs/api/json_pointer/operator_string_t.md b/docs/mkdocs/docs/api/json_pointer/operator_string_t.md index 89898fa4f..9fd8bce94 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_string_t.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_string_t.md @@ -26,6 +26,8 @@ operator string_t() const This function is deprecated in favor of [`to_string`](to_string.md) and will be removed in a future major version release. + See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code. + ## Examples ??? example @@ -44,7 +46,8 @@ operator string_t() const ## See also -- [string_t](../basic_json/string_t.md)- type for strings +- [to_string](to_string.md) return a string representation of the JSON pointer +- [string_t](../basic_json/string_t.md) - type for strings ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/pop_back.md b/docs/mkdocs/docs/api/json_pointer/pop_back.md index 16b1cd4da..6646be44a 100644 --- a/docs/mkdocs/docs/api/json_pointer/pop_back.md +++ b/docs/mkdocs/docs/api/json_pointer/pop_back.md @@ -6,6 +6,10 @@ void pop_back(); Remove the last reference token. +## Exception safety + +Strong exception safety: if an exception occurs, the original value stays intact. + ## Exceptions Throws [out_of_range.405](../../home/exceptions.md#jsonexceptionout_of_range405) if the JSON pointer has no parent. @@ -30,6 +34,12 @@ Constant. --8<-- "examples/json_pointer__pop_back.output" ``` +## See also + +- [back](back.md) return last reference token +- [push_back](push_back.md) append an unescaped token at the end of the pointer +- [parent_pointer](parent_pointer.md) returns the parent of this JSON pointer + ## Version history Added in version 3.6.0. diff --git a/docs/mkdocs/docs/api/json_pointer/pop_front.md b/docs/mkdocs/docs/api/json_pointer/pop_front.md index e77980bb4..308a3cbdc 100644 --- a/docs/mkdocs/docs/api/json_pointer/pop_front.md +++ b/docs/mkdocs/docs/api/json_pointer/pop_front.md @@ -6,6 +6,10 @@ void pop_front(); Remove the first reference token. +## Exception safety + +Strong exception safety: if an exception occurs, the original value stays intact. + ## Exceptions Throws [out_of_range.405](../../home/exceptions.md#jsonexceptionout_of_range405) if the JSON pointer has no parent. @@ -30,6 +34,11 @@ Linear in the number of reference tokens in the `json_pointer`. --8<-- "examples/json_pointer__pop_front.output" ``` +## See also + +- [front](front.md) return first reference token +- [push_front](push_front.md) append an unescaped token at the start of the pointer + ## Version history - Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/json_pointer/push_back.md b/docs/mkdocs/docs/api/json_pointer/push_back.md index c1c19cb8d..f50f10924 100644 --- a/docs/mkdocs/docs/api/json_pointer/push_back.md +++ b/docs/mkdocs/docs/api/json_pointer/push_back.md @@ -13,6 +13,10 @@ Append an unescaped token at the end of the reference pointer. `token` (in) : token to add +## Exception safety + +Strong exception safety: if an exception occurs, the original value stays intact. + ## Complexity Amortized constant. @@ -33,6 +37,13 @@ Amortized constant. --8<-- "examples/json_pointer__push_back.output" ``` +## See also + +- [back](back.md) return last reference token +- [pop_back](pop_back.md) remove the last reference token +- [operator/=](operator_slasheq.md) append to the end of the JSON pointer +- [operator/](operator_slash.md) create JSON Pointer by appending + ## Version history - Added in version 3.6.0. diff --git a/docs/mkdocs/docs/api/json_pointer/push_front.md b/docs/mkdocs/docs/api/json_pointer/push_front.md index 387e86e48..67dd87384 100644 --- a/docs/mkdocs/docs/api/json_pointer/push_front.md +++ b/docs/mkdocs/docs/api/json_pointer/push_front.md @@ -13,6 +13,11 @@ Append an unescaped token at the start of the reference pointer. `token` (in) : token to add +## Exception safety + +Basic guarantee: if an exception is thrown (for instance, if copying the reference token fails), the JSON pointer is +left in a valid state, but its reference tokens may have changed. + ## Complexity Linear in the number of reference tokens in the `json_pointer`. @@ -33,6 +38,11 @@ Linear in the number of reference tokens in the `json_pointer`. --8<-- "examples/json_pointer__push_front.output" ``` +## See also + +- [front](front.md) return first reference token +- [pop_front](pop_front.md) remove the first reference token + ## Version history - Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/json_pointer/string_t.md b/docs/mkdocs/docs/api/json_pointer/string_t.md index c8527bc9c..6808ae540 100644 --- a/docs/mkdocs/docs/api/json_pointer/string_t.md +++ b/docs/mkdocs/docs/api/json_pointer/string_t.md @@ -23,6 +23,11 @@ See [`basic_json::string_t`](../basic_json/string_t.md) for more information. --8<-- "examples/json_pointer__string_t.output" ``` +## See also + +- [basic_json::string_t](../basic_json/string_t.md) type used to store JSON strings +- [to_string](to_string.md) return a string representation of the JSON pointer + ## Version history - Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/json_pointer/to_string.md b/docs/mkdocs/docs/api/json_pointer/to_string.md index fae3abe5f..2f700e1db 100644 --- a/docs/mkdocs/docs/api/json_pointer/to_string.md +++ b/docs/mkdocs/docs/api/json_pointer/to_string.md @@ -10,6 +10,14 @@ Return a string representation of the JSON pointer. A string representation of the JSON pointer +## Exception safety + +Strong exception safety: if an exception occurs, the original value stays intact. + +## Complexity + +Linear in the total length of the reference tokens. + ## Notes For each JSON pointer `ptr`, it holds: @@ -34,6 +42,11 @@ ptr == json_pointer(ptr.to_string()); --8<-- "examples/json_pointer__to_string.output" ``` +## See also + +- [operator string_t](operator_string_t.md) return a string representation of the JSON pointer (deprecated) +- [operator<<](../operator_ltlt.md) write a JSON pointer to a stream + ## Version history - Since version 2.0.0. diff --git a/docs/mkdocs/docs/api/json_sax/binary.md b/docs/mkdocs/docs/api/json_sax/binary.md index fc0980e20..f5310390e 100644 --- a/docs/mkdocs/docs/api/json_sax/binary.md +++ b/docs/mkdocs/docs/api/json_sax/binary.md @@ -35,6 +35,12 @@ It is safe to move the passed binary value. --8<-- "examples/sax_parse__binary.output" ``` +## See also + +- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article +- [Binary Values](../../features/binary_values.md) - the article on binary values + ## Version history - Added in version 3.8.0. diff --git a/docs/mkdocs/docs/api/json_sax/boolean.md b/docs/mkdocs/docs/api/json_sax/boolean.md index fdf294562..89abb67ba 100644 --- a/docs/mkdocs/docs/api/json_sax/boolean.md +++ b/docs/mkdocs/docs/api/json_sax/boolean.md @@ -31,6 +31,11 @@ Whether parsing should proceed. --8<-- "examples/sax_parse.output" ``` +## See also + +- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/end_array.md b/docs/mkdocs/docs/api/json_sax/end_array.md index 9c12e40a5..7010e7259 100644 --- a/docs/mkdocs/docs/api/json_sax/end_array.md +++ b/docs/mkdocs/docs/api/json_sax/end_array.md @@ -26,6 +26,11 @@ Whether parsing should proceed. --8<-- "examples/sax_parse.output" ``` +## See also + +- [start_array](start_array.md) - the beginning of an array was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/end_object.md b/docs/mkdocs/docs/api/json_sax/end_object.md index 601c94a4a..aaaec6d22 100644 --- a/docs/mkdocs/docs/api/json_sax/end_object.md +++ b/docs/mkdocs/docs/api/json_sax/end_object.md @@ -26,6 +26,12 @@ Whether parsing should proceed. --8<-- "examples/sax_parse.output" ``` +## See also + +- [start_object](start_object.md) - the beginning of an object was read +- [key](key.md) - an object key was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/index.md b/docs/mkdocs/docs/api/json_sax/index.md index d66b7a254..19d0a52ac 100644 --- a/docs/mkdocs/docs/api/json_sax/index.md +++ b/docs/mkdocs/docs/api/json_sax/index.md @@ -9,6 +9,27 @@ This class describes the SAX interface used by [sax_parse](../basic_json/sax_par different situations while the input is parsed. The boolean return value informs the parser whether to continue processing the input. +For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order: + +```mermaid +sequenceDiagram + participant P as Parser + participant H as SAX handler + + P->>H: start_object(elements) + P->>H: key("a") + P->>H: start_array(elements) + P->>H: number_unsigned(1) + P->>H: boolean(true) + P->>H: end_array() + P->>H: end_object() +``` + +Note `elements` is passed as `#!cpp std::numeric_limits::max()` (i.e., "unknown") for JSON text input; +only binary formats such as CBOR or MessagePack may report the actual number of elements in `start_object`/ +`start_array`. Also note that `1` is reported via `number_unsigned` rather than `number_integer` because it has no +leading `-` sign. + ## Template parameters `BasicJsonType` diff --git a/docs/mkdocs/docs/api/json_sax/key.md b/docs/mkdocs/docs/api/json_sax/key.md index 31fd6c1d1..5ecea20f9 100644 --- a/docs/mkdocs/docs/api/json_sax/key.md +++ b/docs/mkdocs/docs/api/json_sax/key.md @@ -35,6 +35,12 @@ It is safe to move the passed object key value. --8<-- "examples/sax_parse.output" ``` +## See also + +- [start_object](start_object.md) - the beginning of an object was read +- [end_object](end_object.md) - the end of an object was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/null.md b/docs/mkdocs/docs/api/json_sax/null.md index 9354ede6c..ef5771094 100644 --- a/docs/mkdocs/docs/api/json_sax/null.md +++ b/docs/mkdocs/docs/api/json_sax/null.md @@ -26,6 +26,11 @@ Whether parsing should proceed. --8<-- "examples/sax_parse.output" ``` +## See also + +- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/number_float.md b/docs/mkdocs/docs/api/json_sax/number_float.md index 17799401e..d9c37c257 100644 --- a/docs/mkdocs/docs/api/json_sax/number_float.md +++ b/docs/mkdocs/docs/api/json_sax/number_float.md @@ -34,6 +34,12 @@ Whether parsing should proceed. --8<-- "examples/sax_parse.output" ``` +## See also + +- [number_integer](number_integer.md) - an integer number was read +- [number_unsigned](number_unsigned.md) - an unsigned integer number was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/number_integer.md b/docs/mkdocs/docs/api/json_sax/number_integer.md index 5c3cb4f31..c67f9a410 100644 --- a/docs/mkdocs/docs/api/json_sax/number_integer.md +++ b/docs/mkdocs/docs/api/json_sax/number_integer.md @@ -31,6 +31,12 @@ Whether parsing should proceed. --8<-- "examples/sax_parse.output" ``` +## See also + +- [number_unsigned](number_unsigned.md) - an unsigned integer number was read +- [number_float](number_float.md) - a floating-point number was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/number_unsigned.md b/docs/mkdocs/docs/api/json_sax/number_unsigned.md index 0ac250037..edb40d402 100644 --- a/docs/mkdocs/docs/api/json_sax/number_unsigned.md +++ b/docs/mkdocs/docs/api/json_sax/number_unsigned.md @@ -31,6 +31,12 @@ Whether parsing should proceed. --8<-- "examples/sax_parse.output" ``` +## See also + +- [number_integer](number_integer.md) - an integer number was read +- [number_float](number_float.md) - a floating-point number was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/parse_error.md b/docs/mkdocs/docs/api/json_sax/parse_error.md index e41cb67ff..ce90f4448 100644 --- a/docs/mkdocs/docs/api/json_sax/parse_error.md +++ b/docs/mkdocs/docs/api/json_sax/parse_error.md @@ -39,6 +39,12 @@ Whether parsing should proceed (**must return `#!cpp false`**). --8<-- "examples/sax_parse.output" ``` +## See also + +- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without + exceptions + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/start_array.md b/docs/mkdocs/docs/api/json_sax/start_array.md index 1a221565f..48557fa48 100644 --- a/docs/mkdocs/docs/api/json_sax/start_array.md +++ b/docs/mkdocs/docs/api/json_sax/start_array.md @@ -35,6 +35,11 @@ Binary formats may report the number of elements. --8<-- "examples/sax_parse.output" ``` +## See also + +- [end_array](end_array.md) - the end of an array was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/start_object.md b/docs/mkdocs/docs/api/json_sax/start_object.md index 5ae753805..3a569878f 100644 --- a/docs/mkdocs/docs/api/json_sax/start_object.md +++ b/docs/mkdocs/docs/api/json_sax/start_object.md @@ -35,6 +35,12 @@ Binary formats may report the number of elements. --8<-- "examples/sax_parse.output" ``` +## See also + +- [end_object](end_object.md) - the end of an object was read +- [key](key.md) - an object key was read +- [sax_parse](../basic_json/sax_parse.md) - SAX parser + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/json_sax/string.md b/docs/mkdocs/docs/api/json_sax/string.md index dcffb5f61..7e742ebcb 100644 --- a/docs/mkdocs/docs/api/json_sax/string.md +++ b/docs/mkdocs/docs/api/json_sax/string.md @@ -35,6 +35,11 @@ It is safe to move the passed string value. --8<-- "examples/sax_parse.output" ``` +## See also + +- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article + ## Version history - Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index e917e0ae2..3c9b42af2 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -26,6 +26,7 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_HAS_CPP_11**
**JSON_HAS_CPP_14**
**JSON_HAS_CPP_17**
**JSON_HAS_CPP_20**](json_has_cpp_11.md) - set supported C++ standard - [**JSON_HAS_FILESYSTEM**
**JSON_HAS_EXPERIMENTAL_FILESYSTEM**](json_has_filesystem.md) - control `std::filesystem` support - [**JSON_HAS_RANGES**](json_has_ranges.md) - control `std::ranges` support +- [**JSON_HAS_STATIC_RTTI**](json_has_static_rtti.md) - control RTTI (run time type information) support - [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support - [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support - [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically diff --git a/docs/mkdocs/docs/api/macros/json_assert.md b/docs/mkdocs/docs/api/macros/json_assert.md index 2d7b0c78a..5cd52a091 100644 --- a/docs/mkdocs/docs/api/macros/json_assert.md +++ b/docs/mkdocs/docs/api/macros/json_assert.md @@ -31,7 +31,7 @@ Therefore, assertions can be switched off by defining `NDEBUG`. ## Examples -??? example "Example 1: default behavior" +??? example "Example: default behavior" The following code will trigger an assertion at runtime: @@ -53,7 +53,7 @@ Therefore, assertions can be switched off by defining `NDEBUG`. Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144. ``` -??? example "Example 2: user-defined behavior" +??? example "Example: user-defined behavior" The assertion reporting can be changed by defining `JSON_ASSERT(x)` differently. diff --git a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md index 2301a0486..459818c87 100644 --- a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md +++ b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md @@ -70,7 +70,7 @@ The default value is `0` (disabled — existing behavior is preserved). ## Examples -??? example "Default behavior (macro not defined)" +??? example "Example: default behavior (macro not defined)" Without the macro, single-element brace initialization wraps the value in an array: @@ -88,7 +88,7 @@ The default value is `0` (disabled — existing behavior is preserved). } ``` -??? example "Opt-in copy semantics (macro defined to 1)" +??? example "Example: opt-in copy semantics (macro defined to 1)" With the macro, single-element brace initialization copies/moves the value: diff --git a/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md b/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md index f928f227e..486f3f34e 100644 --- a/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md +++ b/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md @@ -81,7 +81,7 @@ When the macro is not defined, the library will define it to its default value. The output shows the start/end positions of all the objects and fields in the JSON string. -??? example "Example 2: using only diagnostic positions in exceptions" +??? example "Example: using only diagnostic positions in exceptions" ```cpp --8<-- "examples/diagnostic_positions_exception.cpp" @@ -95,7 +95,7 @@ When the macro is not defined, the library will define it to its default value. The output shows the exception with start/end positions only. -??? example "Example 3: using extended diagnostics with positions enabled in exceptions" +??? example "Example: using extended diagnostics with positions enabled in exceptions" ```cpp --8<-- "examples/diagnostics_extended_positions.cpp" diff --git a/docs/mkdocs/docs/api/macros/json_diagnostics.md b/docs/mkdocs/docs/api/macros/json_diagnostics.md index d60df5e44..c68b4380b 100644 --- a/docs/mkdocs/docs/api/macros/json_diagnostics.md +++ b/docs/mkdocs/docs/api/macros/json_diagnostics.md @@ -43,7 +43,7 @@ When the macro is not defined, the library will define it to its default value. ## Examples -??? example "Example 1: default behavior" +??? example "Example: default behavior" ```cpp --8<-- "examples/diagnostics_standard.cpp" @@ -57,7 +57,7 @@ When the macro is not defined, the library will define it to its default value. This exception can be hard to debug if storing the value `#!c "12"` and accessing it is further apart. -??? example "Example 2: extended diagnostic messages" +??? example "Example: extended diagnostic messages" ```cpp --8<-- "examples/diagnostics_extended.cpp" @@ -71,7 +71,7 @@ When the macro is not defined, the library will define it to its default value. Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type. -??? example "Example 3: using only diagnostic positions in exceptions" +??? example "Example: using only diagnostic positions in exceptions" ```cpp --8<-- "examples/diagnostic_positions_exception.cpp" diff --git a/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md b/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md index 2548a03fa..83c48515b 100644 --- a/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md +++ b/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md @@ -30,7 +30,7 @@ The default value is `0`. ## Examples -??? example "Example 1: Disabled behavior" +??? example "Example: Disabled behavior" The code below forces the library **not** to create default serialization/deserialization functions `from_json` and `to_json`, meaning the code below **does not** compile. @@ -57,7 +57,7 @@ The default value is `0`. } ``` -??? example "Example 2: Serialize enum macro" +??? example "Example: Serialize enum macro" The code below forces the library **not** to create default serialization/deserialization functions `from_json` and `to_json`, but uses [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) to parse and serialize the enum. @@ -90,7 +90,7 @@ The default value is `0`. } ``` -??? example "Example 3: User-defined serialization/deserialization functions" +??? example "Example: User-defined serialization/deserialization functions" The code below forces the library **not** to create default serialization/deserialization functions `from_json` and `to_json`, but uses user-defined functions to parse and serialize the enum. diff --git a/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md index c759fd65c..c69240726 100644 --- a/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md +++ b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md @@ -67,7 +67,7 @@ The default value is `0` (disabled — existing behavior is preserved). ## Examples -??? example "Default behavior (macro not defined)" +??? example "Example: default behavior (macro not defined)" ```cpp #include @@ -83,7 +83,7 @@ The default value is `0` (disabled — existing behavior is preserved). } ``` -??? example "Conversion disabled (macro defined to 1)" +??? example "Example: conversion disabled (macro defined to 1)" ```cpp #define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1 diff --git a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md index 053214bb3..6ab33bfe0 100644 --- a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md +++ b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md @@ -40,6 +40,13 @@ The default value is detected based on preprocessor macros such as `#!cpp __cplu ... ``` +## See also + +- [JSON_HAS_FILESYSTEM / JSON_HAS_EXPERIMENTAL_FILESYSTEM](json_has_filesystem.md) - control `std::filesystem` support +- [JSON_HAS_RANGES](json_has_ranges.md) - control `std::ranges` support +- [JSON_HAS_THREE_WAY_COMPARISON](json_has_three_way_comparison.md) - control 3-way comparison support +- [JSON_HAS_STD_FORMAT](json_has_std_format.md) - control `std::format`/`std::formatter` support + ## Version history - Added in version 3.10.5. diff --git a/docs/mkdocs/docs/api/macros/json_has_filesystem.md b/docs/mkdocs/docs/api/macros/json_has_filesystem.md index 68eb3089b..fe843e5c1 100644 --- a/docs/mkdocs/docs/api/macros/json_has_filesystem.md +++ b/docs/mkdocs/docs/api/macros/json_has_filesystem.md @@ -52,6 +52,11 @@ The default value is detected based on the preprocessor macros `#!cpp __cpp_lib_ ... ``` +## See also + +- [JSON_HAS_CPP_11 / JSON_HAS_CPP_14 / JSON_HAS_CPP_17 / JSON_HAS_CPP_20 / JSON_HAS_CPP_23 / + JSON_HAS_CPP_26](json_has_cpp_11.md) - set supported C++ standard + ## Version history - Added in version 3.10.5. diff --git a/docs/mkdocs/docs/api/macros/json_has_ranges.md b/docs/mkdocs/docs/api/macros/json_has_ranges.md index c51fb4f34..0bb3c9b58 100644 --- a/docs/mkdocs/docs/api/macros/json_has_ranges.md +++ b/docs/mkdocs/docs/api/macros/json_has_ranges.md @@ -40,6 +40,13 @@ When the macro is not defined, the library will define it to its default value. ... ``` +## See also + +- [JSON_HAS_CPP_11 / JSON_HAS_CPP_14 / JSON_HAS_CPP_17 / JSON_HAS_CPP_20 / JSON_HAS_CPP_23 / + JSON_HAS_CPP_26](json_has_cpp_11.md) - set supported C++ standard +- [JSON_HAS_STD_FORMAT](json_has_std_format.md) - a similar feature-detection macro, for `std::format`/`std::formatter` + support + ## Version history - Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/macros/json_has_static_rtti.md b/docs/mkdocs/docs/api/macros/json_has_static_rtti.md index 33d0703ab..a44a7c224 100644 --- a/docs/mkdocs/docs/api/macros/json_has_static_rtti.md +++ b/docs/mkdocs/docs/api/macros/json_has_static_rtti.md @@ -25,7 +25,12 @@ When the macro is not defined, the library will define it to its default value. ... ``` - + +## See also + +- [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit); on C++17, this macro + controls whether `std::any` is excluded from its candidate types + ## Version history - Added in version 3.11.3. diff --git a/docs/mkdocs/docs/api/macros/json_has_std_format.md b/docs/mkdocs/docs/api/macros/json_has_std_format.md index 8f61d0fed..0691fe3cd 100644 --- a/docs/mkdocs/docs/api/macros/json_has_std_format.md +++ b/docs/mkdocs/docs/api/macros/json_has_std_format.md @@ -36,6 +36,10 @@ When the macro is not defined, the library will define it to its default value. ... ``` +## See also + +- [`std::formatter`](../basic_json/std_formatter.md) - format JSON values with `std::format` + ## Version history - Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md index f52070ebf..a1c2cdb58 100644 --- a/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md +++ b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md @@ -27,6 +27,11 @@ When the macro is not defined, the library will define it to its default value. ... ``` +## See also + +- [**operator<=>**](../basic_json/operator_spaceship.md) - 3-way compare JSON values +- [**operator==**](../json_pointer/operator_eq.md) - compare JSON pointers for equality + ## Version history - Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/macros/json_no_io.md b/docs/mkdocs/docs/api/macros/json_no_io.md index ef37384a5..ed76672ac 100644 --- a/docs/mkdocs/docs/api/macros/json_no_io.md +++ b/docs/mkdocs/docs/api/macros/json_no_io.md @@ -30,6 +30,11 @@ By default, `#!cpp JSON_NO_IO` is not defined. ... ``` +## See also + +- [**operator<<**](../operator_ltlt.md) - serialize to stream +- [**operator>>**](../operator_gtgt.md) - deserialize from stream + ## Version history - Added in version 3.10.0. diff --git a/docs/mkdocs/docs/api/macros/json_no_thread_local.md b/docs/mkdocs/docs/api/macros/json_no_thread_local.md index 0a001ac36..f4fb28dbb 100644 --- a/docs/mkdocs/docs/api/macros/json_no_thread_local.md +++ b/docs/mkdocs/docs/api/macros/json_no_thread_local.md @@ -43,6 +43,10 @@ Copying and comparing fall back to working without the call stack there, as they ... ``` +## See also + +- [FAQ: Thread safety](../../home/faq.md#thread-safety) + ## Version history -- Added in version 3.12.1. +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/json_precise_stream_position.md b/docs/mkdocs/docs/api/macros/json_precise_stream_position.md index 5710e9975..b3145fc66 100644 --- a/docs/mkdocs/docs/api/macros/json_precise_stream_position.md +++ b/docs/mkdocs/docs/api/macros/json_precise_stream_position.md @@ -79,7 +79,7 @@ The default value is `0` (disabled — existing behavior is preserved). ## Examples -??? example "Default behavior (macro not defined)" +??? example "Example: default behavior (macro not defined)" Without the macro, the character after a number is consumed: @@ -99,7 +99,7 @@ The default value is `0` (disabled — existing behavior is preserved). } ``` -??? example "Opt-in precise stream position (macro defined to 1)" +??? example "Example: opt-in precise stream position (macro defined to 1)" With the macro, the stream is positioned right after the number: diff --git a/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md b/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md index 37e91b3c6..88f272334 100644 --- a/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md +++ b/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md @@ -24,7 +24,7 @@ By default, the macro is not defined. ## Examples -??? example +??? example "Example: switch off the version check" The code below switches off the warning about including a different version of the library. @@ -35,7 +35,7 @@ By default, the macro is not defined. ... ``` -!!! example +??? example "Example: warning about a different library version" The following warning will be shown in case a different version of the library was already included: @@ -43,6 +43,11 @@ By default, the macro is not defined. Already included a different version of the library! ``` +## See also + +- [NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, + NLOHMANN_JSON_VERSION_PATCH](nlohmann_json_version_major.md) - library version information + ## Version history -Added in version 3.11.0. +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md b/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md index 52cdbd172..f5702a454 100644 --- a/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md +++ b/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md @@ -28,6 +28,11 @@ By default, the macro is not defined. ... ``` +## See also + +- [JSON_HAS_CPP_11 / JSON_HAS_CPP_14 / JSON_HAS_CPP_17 / JSON_HAS_CPP_20 / JSON_HAS_CPP_23 / + JSON_HAS_CPP_26](json_has_cpp_11.md) - set supported C++ standard + ## Version history -Added in version 3.2.0. +- Added in version 3.2.0. diff --git a/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md b/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md index 3bad2dea1..69af3811d 100644 --- a/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md +++ b/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md @@ -85,7 +85,7 @@ The default value is `0` (disabled — existing behavior is preserved). ## Examples -??? example "Default behavior (macro not defined)" +??? example "Example: default behavior (macro not defined)" Without the macro, a NUL byte silently ends parsing at that point: @@ -101,7 +101,7 @@ The default value is `0` (disabled — existing behavior is preserved). } ``` -??? example "Opt-in strict handling (macro defined to 1)" +??? example "Example: opt-in strict handling (macro defined to 1)" With the macro, a NUL byte is rejected like any other unexpected byte: @@ -124,6 +124,7 @@ The default value is `0` (disabled — existing behavior is preserved). ## See also +- [:simple-cmake: JSON_StrictNulHandling](../../integration/cmake.md#json_strictnulhandling) - CMake option to control the macro - [FAQ: NUL bytes in the input](../../home/faq.md#nul-bytes-in-the-input) - [**parse**](../basic_json/parse.md) - deserialize from a compatible input - [**accept**](../basic_json/accept.md) - check if the input is valid JSON diff --git a/docs/mkdocs/docs/api/macros/json_use_global_udls.md b/docs/mkdocs/docs/api/macros/json_use_global_udls.md index 0b055640e..6b1c2c7d8 100644 --- a/docs/mkdocs/docs/api/macros/json_use_global_udls.md +++ b/docs/mkdocs/docs/api/macros/json_use_global_udls.md @@ -26,6 +26,8 @@ When the macro is not defined, the library behaves as if it were defined to its To prepare existing code, define `JSON_USE_GLOBAL_UDLS` to `0` and bring the string literals into scope where needed. Refer to any of the [string literals](#see-also) for details. + See the [migration guide](../../integration/migration_guide.md#import-namespace-literals-for-udls) for how to update existing code. + !!! hint "CMake option" The placement of user-defined string literals can also be controlled with the CMake option @@ -39,7 +41,7 @@ When the macro is not defined, the library behaves as if it were defined to its ## Examples -??? example "Example 1: Default behavior" +??? example "Example: Default behavior" The code below shows the default behavior using the `_json` UDL. @@ -62,7 +64,7 @@ When the macro is not defined, the library behaves as if it were defined to its 42 ``` -??? example "Example 2: Namespaced UDLs" +??? example "Example: Namespaced UDLs" The code below shows how UDLs need to be brought into scope before using `_json` when `JSON_USE_GLOBAL_UDLS` is defined to `0`. diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index e3d5fb29d..5b74f1ff7 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -24,6 +24,8 @@ By default, implicit conversions are enabled. You can prepare existing code by already defining `JSON_USE_IMPLICIT_CONVERSIONS` to `0` and replace any implicit conversions with calls to [`get`](../basic_json/get.md). + See the [migration guide](../../integration/migration_guide.md#replace-implicit-conversions) for how to update existing code. + !!! tip "Automatic migration" The community-maintained clang-tidy check `modernize-nlohmann-json-explicit-conversions` rewrites implicit diff --git a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md index f026a9825..b6efd8dbd 100644 --- a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md +++ b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md @@ -53,6 +53,8 @@ When the macro is not defined, the library will define it to its default value. New code should not depend on it and existing code should try to remove or rewrite expressions relying on it. + See the [migration guide](../../integration/migration_guide.md#miscellaneous-functions) for how to update existing code. + !!! hint "CMake option" Legacy comparison can also be controlled with the CMake option diff --git a/docs/mkdocs/docs/api/macros/json_use_simdutf.md b/docs/mkdocs/docs/api/macros/json_use_simdutf.md index 611c1e9f1..99dc09892 100644 --- a/docs/mkdocs/docs/api/macros/json_use_simdutf.md +++ b/docs/mkdocs/docs/api/macros/json_use_simdutf.md @@ -62,9 +62,15 @@ By default, `#!cpp JSON_USE_SIMDUTF` is not defined and the portable C++11 scala !!! hint "Testing this configuration" - The unit tests can be built against the simdutf backend with the CMake option `JSON_TestSimdutf` (`OFF` by - default), which fetches simdutf and defines `JSON_USE_SIMDUTF` for every test target. The `ci_test_simdutf` target - runs the whole test suite in that configuration. + The unit tests can be built against the simdutf backend with the CMake option + [`JSON_TestSimdutf`](../../integration/cmake.md#json_testsimdutf) (`OFF` by default), which fetches simdutf and + defines `JSON_USE_SIMDUTF` for every test target. The `ci_test_simdutf` target runs the whole test suite in that + configuration. + +## See also + +- [:simple-cmake: JSON_TestSimdutf](../../integration/cmake.md#json_testsimdutf) - CMake option to build the unit + tests against the simdutf backend ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md index 8c549d137..f6a01af5b 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md @@ -149,7 +149,7 @@ void to_json(BasicJsonType& j, const B& b) { ## Examples -??? example "NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE" +??? example "Example: (1) NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE" Consider the following complete example: diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md index deef3fec9..3db7722d8 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md @@ -83,7 +83,7 @@ See the examples below for the concrete generated code. ## Examples -??? example "Example (1): NLOHMANN_DEFINE_TYPE_INTRUSIVE" +??? example "Example: (1) NLOHMANN_DEFINE_TYPE_INTRUSIVE" Consider the following complete example: @@ -112,7 +112,7 @@ See the examples below for the concrete generated code. --8<-- "examples/nlohmann_define_type_intrusive_explicit.cpp" ``` -??? example "Example (2): NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT" +??? example "Example: (2) NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT" Consider the following complete example: @@ -142,7 +142,7 @@ See the examples below for the concrete generated code. Note how a default-initialized `person` object is used in the `from_json` to fill missing values. -??? example "Example (3): NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE" +??? example "Example: (3) NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE" Consider the following complete example: ```cpp hl_lines="22" diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md index 6a29c9e66..744a513b0 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md @@ -82,7 +82,7 @@ See the examples below for the concrete generated code. ## Examples -??? example "Example (1): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE" +??? example "Example: (1) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE" Consider the following complete example: @@ -110,7 +110,7 @@ See the examples below for the concrete generated code. --8<-- "examples/nlohmann_define_type_non_intrusive_explicit.cpp" ``` -??? example "Example (2): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT" +??? example "Example: (2) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT" Consider the following complete example: @@ -141,7 +141,7 @@ See the examples below for the concrete generated code. Note how a default-initialized `person` object is used in the `from_json` to fill missing values. -??? example "Example (3): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE" +??? example "Example: (3) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE" Consider the following complete example: diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md index 9ce239645..9ee23c6dd 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md @@ -45,7 +45,7 @@ For further information please refer to the corresponding macros without `WITH_N ## Examples -??? example "Example (1): NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES" +??? example "Example: NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES" Consider the following complete example: @@ -73,6 +73,21 @@ For further information please refer to the corresponding macros without `WITH_N --8<-- "examples/nlohmann_define_type_non_intrusive_with_names_explicit.cpp" ``` +## See also + +- [NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, + NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_intrusive.md) - the macros these variants add + custom JSON key names to +- [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_non_intrusive.md) - the macros these + variants add custom JSON key names to +- [NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, + NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, + NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_derived_type.md) - similar macros for + derived types, also available with custom names +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - overview of type conversion mechanisms + ## Version history -1. Added in version 3.13.0. +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md index 915a20a5f..626055fdf 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md @@ -44,7 +44,7 @@ inline void from_json(const BasicJsonType& j, type& e); ## Examples -??? example "Example 1: Basic usage" +??? example "Example: Basic usage" The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM` can be used to serialize/deserialize both classical enums and C++11 enum classes: @@ -59,7 +59,7 @@ inline void from_json(const BasicJsonType& j, type& e); --8<-- "examples/nlohmann_json_serialize_enum.output" ``` -??? example "Example 2: Multiple conversions for one enumerator" +??? example "Example: Multiple conversions for one enumerator" The example shows how to use multiple conversions for a single enumerator. In the example, `Color::red` will always be *serialized* to `"red"`, because the first occurring conversion. The second conversion, however, offers an diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md index 73a3a7da4..4697c5a95 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md @@ -47,7 +47,7 @@ inline void from_json(const BasicJsonType& j, type& e); ## Examples -??? example "Example 1: Basic usage" +??? example "Example: Basic usage" The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` can be used to serialize/deserialize both classical enums and C++11 enum classes: @@ -62,7 +62,7 @@ inline void from_json(const BasicJsonType& j, type& e); --8<-- "examples/nlohmann_json_serialize_enum_strict.output" ``` -??? example "Example 2: Multiple conversions for one enumerator" +??? example "Example: Multiple conversions for one enumerator" The example shows how to use multiple conversions for a single enumerator. In the example, `Color::red` will always be *serialized* to `"red"`, because the first occurring conversion. The second conversion, however, offers an @@ -78,7 +78,7 @@ inline void from_json(const BasicJsonType& j, type& e); --8<-- "examples/nlohmann_json_serialize_enum_strict_2.output" ``` -??? example "Example 3: exceptions on invalid serialization" +??? example "Example: exceptions on invalid serialization" The example shows how an invalid serialization causes an exception to be thrown. In the example, Color::unknown is not defined in the mapping used to call `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` diff --git a/docs/mkdocs/docs/api/operator_gtgt.md b/docs/mkdocs/docs/api/operator_gtgt.md index 35b8c1016..334d9539e 100644 --- a/docs/mkdocs/docs/api/operator_gtgt.md +++ b/docs/mkdocs/docs/api/operator_gtgt.md @@ -96,6 +96,8 @@ being read. been deprecated in version 3.0.0. It will be removed in version 4.0.0. Please replace calls like `#!cpp j << i;` with `#!cpp i >> j;`. + See the [migration guide](../integration/migration_guide.md#parsing) for how to update existing code. + ## Examples ??? example diff --git a/docs/mkdocs/docs/api/operator_literal_json.md b/docs/mkdocs/docs/api/operator_literal_json.md index a909d8165..4e4002aa3 100644 --- a/docs/mkdocs/docs/api/operator_literal_json.md +++ b/docs/mkdocs/docs/api/operator_literal_json.md @@ -18,8 +18,9 @@ using namespace nlohmann; ``` This is suggested to ease migration to the next major version release of the library. See -[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) for details. The operator is declared in header -``, which `` includes unless +[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) and the +[migration guide](../integration/migration_guide.md#import-namespace-literals-for-udls) for details. The operator is +declared in header ``, which `` includes unless [`JSON_NO_AUTOMATIC_UDLS`](macros/json_no_automatic_udls.md) is defined. ## Parameters @@ -68,4 +69,4 @@ Linear. - Added in version 1.0.0. - Moved to namespace `nlohmann::literals::json_literals` in 3.11.0. -- Added `char8_t*` overload in 3.13.0. +- Added `char8_t*` overload in version 3.13.0. diff --git a/docs/mkdocs/docs/api/operator_literal_json_pointer.md b/docs/mkdocs/docs/api/operator_literal_json_pointer.md index 0494439aa..be36b24ed 100644 --- a/docs/mkdocs/docs/api/operator_literal_json_pointer.md +++ b/docs/mkdocs/docs/api/operator_literal_json_pointer.md @@ -17,8 +17,9 @@ using namespace nlohmann::literals::json_literals; using namespace nlohmann; ``` This is suggested to ease migration to the next major version release of the library. See -[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) for details. The operator is declared in header -``, which `` includes unless +[`JSON_USE_GLOBAL_UDLS`](macros/json_use_global_udls.md#notes) and the +[migration guide](../integration/migration_guide.md#import-namespace-literals-for-udls) for details. The operator is +declared in header ``, which `` includes unless [`JSON_NO_AUTOMATIC_UDLS`](macros/json_no_automatic_udls.md) is defined. ## Parameters @@ -67,4 +68,4 @@ Linear. - Added in version 2.0.0. - Moved to namespace `nlohmann::literals::json_literals` in 3.11.0. -- Added `char8_t*` overload in 3.13.0. +- Added `char8_t*` overload in version 3.13.0. diff --git a/docs/mkdocs/docs/api/operator_ltlt.md b/docs/mkdocs/docs/api/operator_ltlt.md index 1f99493d9..da53d7ee1 100644 --- a/docs/mkdocs/docs/api/operator_ltlt.md +++ b/docs/mkdocs/docs/api/operator_ltlt.md @@ -51,6 +51,8 @@ Linear. `#!cpp std::ostream& operator>>(const basic_json& j, std::ostream& o)` which has been deprecated in version 3.0.0. It will be removed in version 4.0.0. Please replace calls like `#!cpp j >> o;` with `#!cpp o << j;`. + See the [migration guide](../integration/migration_guide.md#miscellaneous-functions) for how to update existing code. + ## Examples ??? example "Example: (1) serialize JSON value to stream" diff --git a/docs/mkdocs/docs/api/ordered_map.md b/docs/mkdocs/docs/api/ordered_map.md index e464d0b1e..a5e07e60a 100644 --- a/docs/mkdocs/docs/api/ordered_map.md +++ b/docs/mkdocs/docs/api/ordered_map.md @@ -104,8 +104,8 @@ This differs from `#!cpp std::map`, where the same operations are O(log n). | 16 000 | 3.3 ms | 181.6 ms | 54Ɨ | If key order matters for objects of that size, consider a container with a lookup index, such as - [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) - ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)), as the object type -- see + [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) + ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)), as the object type -- see [object order](../features/object_order.md). ## Examples diff --git a/docs/mkdocs/docs/community/assurance_case.md b/docs/mkdocs/docs/community/assurance_case.md index 10f4689c1..9d9712a03 100644 --- a/docs/mkdocs/docs/community/assurance_case.md +++ b/docs/mkdocs/docs/community/assurance_case.md @@ -30,6 +30,17 @@ that an attacker controls, passed to [`parse`](../api/basic_json/parse.md), [`ac not a security boundary. Such preconditions are checked with [runtime assertions](../features/assertions.md) in debug builds; functions such as [`at`](../api/basic_json/at.md) offer checked access with exceptions. +```mermaid +flowchart LR + A[Untrusted input] --> B[Parser] + A --> C[SAX interface] + A --> D[Binary readers] + B --> E["Value tree (basic_json)"] + C --> E + D --> E + E --> F[Trusted caller] +``` + ## Secure design - **Strict parsing.** The parser accepts exactly the JSON grammar of [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259). diff --git a/docs/mkdocs/docs/css/custom.css b/docs/mkdocs/docs/css/custom.css index 8940a782e..1fec0e57c 100644 --- a/docs/mkdocs/docs/css/custom.css +++ b/docs/mkdocs/docs/css/custom.css @@ -2,3 +2,19 @@ .md-typeset code, .md-typeset pre { font-variant-ligatures: common-ligatures; } + +/* badge after version numbers newer than the latest release (hooks/unreleased_versions.py) */ +.md-typeset .unreleased-version { + display: inline-block; + padding: 0 .5em; + border: .05rem solid var(--md-accent-fg-color); + border-radius: 1em; + background-color: var(--md-accent-fg-color--transparent); + color: var(--md-typeset-color); + font-size: .7em; + font-weight: 700; + line-height: 1.6; + vertical-align: .1em; + white-space: nowrap; + cursor: help; +} diff --git a/docs/mkdocs/docs/examples/accept__iterator_pair.cpp b/docs/mkdocs/docs/examples/accept__iterator_pair.cpp new file mode 100644 index 000000000..dc7320de6 --- /dev/null +++ b/docs/mkdocs/docs/examples/accept__iterator_pair.cpp @@ -0,0 +1,15 @@ +#include +#include +#include + +using json = nlohmann::json; + +int main() +{ + // a buffer containing a JSON text followed by more data + std::vector input = {'[', '1', ',', '2', ',', '3', ']', 'o', 't', 'h', 'e', 'r'}; + + std::cout << std::boolalpha + << json::accept(input.begin(), input.begin() + 7) << ' ' + << json::accept(input.begin(), input.end()) << '\n'; +} diff --git a/docs/mkdocs/docs/examples/accept__iterator_pair.output b/docs/mkdocs/docs/examples/accept__iterator_pair.output new file mode 100644 index 000000000..836a5934c --- /dev/null +++ b/docs/mkdocs/docs/examples/accept__iterator_pair.output @@ -0,0 +1 @@ +true false diff --git a/docs/mkdocs/docs/examples/basic_json__BasicJsonType.cpp b/docs/mkdocs/docs/examples/basic_json__BasicJsonType.cpp new file mode 100644 index 000000000..562d45b9f --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json__BasicJsonType.cpp @@ -0,0 +1,22 @@ +#include +#include + +using json = nlohmann::json; +using ordered_json = nlohmann::ordered_json; + +int main() +{ + // create an ordered_json value; insertion order is preserved + ordered_json oj = {{"c", 3}, {"a", 1}, {"b", 2}}; + + // convert to json -- overload (4) is used; keys end up sorted + json j(oj); + + // convert back to ordered_json -- the original insertion order is lost, + // because it was already given up when converting to json + ordered_json oj2(j); + + std::cout << oj << '\n'; + std::cout << j << '\n'; + std::cout << oj2 << '\n'; +} diff --git a/docs/mkdocs/docs/examples/basic_json__BasicJsonType.output b/docs/mkdocs/docs/examples/basic_json__BasicJsonType.output new file mode 100644 index 000000000..ab9841756 --- /dev/null +++ b/docs/mkdocs/docs/examples/basic_json__BasicJsonType.output @@ -0,0 +1,3 @@ +{"c":3,"a":1,"b":2} +{"a":1,"b":2,"c":3} +{"a":1,"b":2,"c":3} diff --git a/docs/mkdocs/docs/examples/basic_json__CompatibleType.cpp b/docs/mkdocs/docs/examples/basic_json__CompatibleType.cpp index f0d0cc1e7..130ef6aec 100644 --- a/docs/mkdocs/docs/examples/basic_json__CompatibleType.cpp +++ b/docs/mkdocs/docs/examples/basic_json__CompatibleType.cpp @@ -44,7 +44,7 @@ int main() // create an object from std::unordered_multimap std::unordered_multimap c_ummap { - {"one", true}, {"two", true}, {"three", false}, {"three", true} + {"one", true}, {"two", true}, {"three", false}, {"three", false} }; json j_ummap(c_ummap); // only one entry for key "three" is used @@ -92,7 +92,7 @@ int main() json j_set(c_set); // only one entry for "one" is used // create an array from std::unordered_set - std::unordered_set c_uset {"one", "two", "three", "four", "one"}; + std::unordered_set c_uset {"one", "one"}; json j_uset(c_uset); // only one entry for "one" is used // create an array from std::multiset @@ -100,7 +100,7 @@ int main() json j_mset(c_mset); // both entries for "one" are used // create an array from std::unordered_multiset - std::unordered_multiset c_umset {"one", "two", "one", "four"}; + std::unordered_multiset c_umset {"one", "one"}; json j_umset(c_umset); // both entries for "one" are used // serialize the JSON arrays diff --git a/docs/mkdocs/docs/examples/basic_json__CompatibleType.output b/docs/mkdocs/docs/examples/basic_json__CompatibleType.output index 2337e81fb..558d992a6 100644 --- a/docs/mkdocs/docs/examples/basic_json__CompatibleType.output +++ b/docs/mkdocs/docs/examples/basic_json__CompatibleType.output @@ -12,9 +12,9 @@ [12345678909876,23456789098765,34567890987654,45678909876543] [1,2,3,4] ["four","one","three","two"] -["four","three","two","one"] +["one"] ["four","one","one","two"] -["four","two","one","one"] +["one","one"] "The quick brown fox jumps over the lazy dog." "The quick brown fox jumps over the lazy dog." diff --git a/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.cpp b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.cpp new file mode 100644 index 000000000..ef2a9cdcb --- /dev/null +++ b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.cpp @@ -0,0 +1,23 @@ +#include +#include + +// define a byte container based on std::vector +using byte_container_with_subtype = nlohmann::byte_container_with_subtype>; + +int main() +{ + std::vector bytes = {{0xca, 0xfe, 0xba, 0xbe}}; + + // create containers without and with a subtype + auto c1 = byte_container_with_subtype(bytes); + auto c2 = byte_container_with_subtype(bytes); + auto c3 = byte_container_with_subtype(bytes, 42); + auto c4 = byte_container_with_subtype(bytes, 42); + auto c5 = byte_container_with_subtype(bytes, 23); + + std::cout << std::boolalpha + << "c1 == c2: " << (c1 == c2) << '\n' + << "c1 == c3: " << (c1 == c3) << '\n' + << "c3 == c4: " << (c3 == c4) << '\n' + << "c3 == c5: " << (c3 == c5) << std::endl; +} diff --git a/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.output b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.output new file mode 100644 index 000000000..32a281680 --- /dev/null +++ b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__equal.output @@ -0,0 +1,4 @@ +c1 == c2: true +c1 == c3: false +c3 == c4: true +c3 == c5: false diff --git a/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.cpp b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.cpp new file mode 100644 index 000000000..c7c104342 --- /dev/null +++ b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.cpp @@ -0,0 +1,23 @@ +#include +#include + +// define a byte container based on std::vector +using byte_container_with_subtype = nlohmann::byte_container_with_subtype>; + +int main() +{ + std::vector bytes = {{0xca, 0xfe, 0xba, 0xbe}}; + + // create containers without and with a subtype + auto c1 = byte_container_with_subtype(bytes); + auto c2 = byte_container_with_subtype(bytes); + auto c3 = byte_container_with_subtype(bytes, 42); + auto c4 = byte_container_with_subtype(bytes, 42); + auto c5 = byte_container_with_subtype(bytes, 23); + + std::cout << std::boolalpha + << "c1 != c2: " << (c1 != c2) << '\n' + << "c1 != c3: " << (c1 != c3) << '\n' + << "c3 != c4: " << (c3 != c4) << '\n' + << "c3 != c5: " << (c3 != c5) << std::endl; +} diff --git a/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.output b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.output new file mode 100644 index 000000000..61fcbbc1c --- /dev/null +++ b/docs/mkdocs/docs/examples/byte_container_with_subtype__operator__notequal.output @@ -0,0 +1,4 @@ +c1 != c2: false +c1 != c3: true +c3 != c4: false +c3 != c5: true diff --git a/docs/mkdocs/docs/examples/custom_string_type.hpp b/docs/mkdocs/docs/examples/custom_string_type.hpp index ec48501fc..f09fe0f98 100644 --- a/docs/mkdocs/docs/examples/custom_string_type.hpp +++ b/docs/mkdocs/docs/examples/custom_string_type.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -8,10 +9,10 @@ // and nothing more of std::string's interface. // // Covers the "Always required" members, the extras needed for the binary -// formats, and the extras needed for JSON Pointer / flatten / unflatten / -// diff. Extending it further (e.g. for std::hash or to_bson) is -// a matter of adding the extra members listed in the "Required for other -// functionality" table. +// formats, JSON Pointer / flatten / unflatten, and the int_to_string overload +// needed for diff and items. Extending it further (e.g. for +// std::hash or to_bson) is a matter of adding the extra members +// listed in the "Required for other functionality" table. // // See https://json.nlohmann.me/features/types/template_parameters/#stringtype class custom_string_type @@ -93,6 +94,11 @@ class custom_string_type data_.append(other.data_); return *this; } + custom_string_type& operator+=(char c) + { + data_.push_back(c); + return *this; + } size_type find_first_of(char c, size_type pos = 0) const { @@ -116,6 +122,12 @@ class custom_string_type return data_.end(); } + // found by ADL; converts array indices to keys in diff and items + friend void int_to_string(custom_string_type& target, std::size_t value) + { + target.data_ = std::to_string(value); + } + friend bool operator==(const custom_string_type& lhs, const custom_string_type& rhs) { return lhs.data_ == rhs.data_; diff --git a/docs/mkdocs/docs/examples/flatten__empty.cpp b/docs/mkdocs/docs/examples/flatten__empty.cpp new file mode 100644 index 000000000..1dd52d551 --- /dev/null +++ b/docs/mkdocs/docs/examples/flatten__empty.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value with an empty object and an empty array + json j = + { + {"empty_object", json::object()}, + {"empty_array", json::array()}, + {"name", "Niels"} + }; + + // call flatten() + json flattened = j.flatten(); + std::cout << std::setw(4) << flattened << "\n\n"; + + // the empty containers cannot be restored by unflatten() + std::cout << std::setw(4) << flattened.unflatten() << '\n'; +} diff --git a/docs/mkdocs/docs/examples/flatten__empty.output b/docs/mkdocs/docs/examples/flatten__empty.output new file mode 100644 index 000000000..ad5230f2c --- /dev/null +++ b/docs/mkdocs/docs/examples/flatten__empty.output @@ -0,0 +1,11 @@ +{ + "/empty_array": null, + "/empty_object": null, + "/name": "Niels" +} + +{ + "empty_array": null, + "empty_object": null, + "name": "Niels" +} diff --git a/docs/mkdocs/docs/examples/get__BasicJsonType.cpp b/docs/mkdocs/docs/examples/get__BasicJsonType.cpp new file mode 100644 index 000000000..d63458c0b --- /dev/null +++ b/docs/mkdocs/docs/examples/get__BasicJsonType.cpp @@ -0,0 +1,17 @@ +#include +#include + +using json = nlohmann::json; +using ordered_json = nlohmann::ordered_json; + +int main() +{ + // create a JSON value + json j = {{"one", 1}, {"two", 2}, {"three", 3}}; + + // convert to a different basic_json specialization + ordered_json oj = j.get(); + + std::cout << j << '\n'; + std::cout << oj << '\n'; +} diff --git a/docs/mkdocs/docs/examples/get__BasicJsonType.output b/docs/mkdocs/docs/examples/get__BasicJsonType.output new file mode 100644 index 000000000..85940d5ab --- /dev/null +++ b/docs/mkdocs/docs/examples/get__BasicJsonType.output @@ -0,0 +1,2 @@ +{"one":1,"three":3,"two":2} +{"one":1,"three":3,"two":2} diff --git a/docs/mkdocs/docs/examples/get__ValueType_const.cpp b/docs/mkdocs/docs/examples/get__ValueType_const.cpp index 7a703aaeb..7b0ca47bb 100644 --- a/docs/mkdocs/docs/examples/get__ValueType_const.cpp +++ b/docs/mkdocs/docs/examples/get__ValueType_const.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include using json = nlohmann::json; @@ -29,7 +29,7 @@ int main() auto v5 = json_types["number"]["floating-point"].get(); auto v6 = json_types["string"].get(); auto v7 = json_types["array"].get>(); - auto v8 = json_types.get>(); + auto v8 = json_types.get>(); // print the conversion results std::cout << v1 << '\n'; diff --git a/docs/mkdocs/docs/examples/get__ValueType_const.output b/docs/mkdocs/docs/examples/get__ValueType_const.output index e7e9b5d59..72a2147f6 100644 --- a/docs/mkdocs/docs/examples/get__ValueType_const.output +++ b/docs/mkdocs/docs/examples/get__ValueType_const.output @@ -4,8 +4,8 @@ Hello, world! 1 2 3 4 5 -number: {"floating-point":17.23,"integer":42} -null: null -string: "Hello, world!" -boolean: true array: [1,2,3,4,5] +boolean: true +null: null +number: {"floating-point":17.23,"integer":42} +string: "Hello, world!" diff --git a/docs/mkdocs/docs/examples/get_to.cpp b/docs/mkdocs/docs/examples/get_to.cpp index 358c8d43a..cdbe347c0 100644 --- a/docs/mkdocs/docs/examples/get_to.cpp +++ b/docs/mkdocs/docs/examples/get_to.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include using json = nlohmann::json; @@ -28,7 +28,7 @@ int main() int v5; std::string v6; std::vector v7; - std::unordered_map v8; + std::map v8; // use explicit conversions json_types["boolean"].get_to(v1); diff --git a/docs/mkdocs/docs/examples/get_to.output b/docs/mkdocs/docs/examples/get_to.output index e7e9b5d59..72a2147f6 100644 --- a/docs/mkdocs/docs/examples/get_to.output +++ b/docs/mkdocs/docs/examples/get_to.output @@ -4,8 +4,8 @@ Hello, world! 1 2 3 4 5 -number: {"floating-point":17.23,"integer":42} -null: null -string: "Hello, world!" -boolean: true array: [1,2,3,4,5] +boolean: true +null: null +number: {"floating-point":17.23,"integer":42} +string: "Hello, world!" diff --git a/docs/mkdocs/docs/examples/is_discarded__parse.cpp b/docs/mkdocs/docs/examples/is_discarded__parse.cpp new file mode 100644 index 000000000..feaca2363 --- /dev/null +++ b/docs/mkdocs/docs/examples/is_discarded__parse.cpp @@ -0,0 +1,22 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // parsing invalid JSON without exceptions yields a discarded value + json j_invalid = json::parse("[1,2,3", nullptr, false); + + // a callback that discards the top-level value does not leave it + // "discarded" -- it is replaced by null instead + json j_discarded_by_callback = json::parse("[1,2,3]", [](int /*depth*/, json::parse_event_t event, json& /*parsed*/) + { + return event != json::parse_event_t::array_start; + }); + + std::cout << std::boolalpha; + std::cout << "j_invalid.is_discarded() = " << j_invalid.is_discarded() << '\n'; + std::cout << "j_discarded_by_callback = " << j_discarded_by_callback << '\n'; + std::cout << "j_discarded_by_callback.is_discarded() = " << j_discarded_by_callback.is_discarded() << '\n'; +} diff --git a/docs/mkdocs/docs/examples/is_discarded__parse.output b/docs/mkdocs/docs/examples/is_discarded__parse.output new file mode 100644 index 000000000..afb367950 --- /dev/null +++ b/docs/mkdocs/docs/examples/is_discarded__parse.output @@ -0,0 +1,3 @@ +j_invalid.is_discarded() = true +j_discarded_by_callback = null +j_discarded_by_callback.is_discarded() = false diff --git a/docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.cpp b/docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.cpp new file mode 100644 index 000000000..a2354da73 --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +using json = nlohmann::json; + +const char* to_string(const std::strong_ordering& so) +{ + if (std::is_lt(so)) + { + return "less"; + } + else if (std::is_gt(so)) + { + return "greater"; + } + return "equal"; +} + +int main() +{ + // different JSON pointers + json::json_pointer ptr1("/a/b"); + json::json_pointer ptr2("/a/c"); + json::json_pointer ptr3("/a/b/c"); + json::json_pointer ptr4("/a/b"); + + // 3-way compare JSON pointers + std::cout << "\"" << ptr1 << "\" <=> \"" << ptr2 << "\": " << to_string(ptr1 <=> ptr2) << '\n' // *NOPAD* + << "\"" << ptr1 << "\" <=> \"" << ptr3 << "\": " << to_string(ptr1 <=> ptr3) << '\n' // *NOPAD* + << "\"" << ptr1 << "\" <=> \"" << ptr4 << "\": " << to_string(ptr1 <=> ptr4) << std::endl; // *NOPAD* +} diff --git a/docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.output b/docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.output new file mode 100644 index 000000000..083d285d0 --- /dev/null +++ b/docs/mkdocs/docs/examples/json_pointer__operator_spaceship.c++20.output @@ -0,0 +1,3 @@ +"/a/b" <=> "/a/c": less +"/a/b" <=> "/a/b/c": less +"/a/b" <=> "/a/b": equal diff --git a/docs/mkdocs/docs/examples/operator__ValueType.cpp b/docs/mkdocs/docs/examples/operator__ValueType.cpp index e8a1d349d..7ab29ec71 100644 --- a/docs/mkdocs/docs/examples/operator__ValueType.cpp +++ b/docs/mkdocs/docs/examples/operator__ValueType.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include using json = nlohmann::json; @@ -29,7 +29,7 @@ int main() int v5 = json_types["number"]["floating-point"]; std::string v6 = json_types["string"]; std::vector v7 = json_types["array"]; - std::unordered_map v8 = json_types; + std::map v8 = json_types; // print the conversion results std::cout << v1 << '\n'; diff --git a/docs/mkdocs/docs/examples/operator__ValueType.output b/docs/mkdocs/docs/examples/operator__ValueType.output index de471ec02..c6fb1500e 100644 --- a/docs/mkdocs/docs/examples/operator__ValueType.output +++ b/docs/mkdocs/docs/examples/operator__ValueType.output @@ -4,9 +4,9 @@ Hello, world! 1 2 3 4 5 -number: {"floating-point":17.23,"integer":42} -null: null -string: "Hello, world!" -boolean: true array: [1,2,3,4,5] +boolean: true +null: null +number: {"floating-point":17.23,"integer":42} +string: "Hello, world!" [json.exception.type_error.302] type must be boolean, but is string diff --git a/docs/mkdocs/docs/examples/parse_event_t.cpp b/docs/mkdocs/docs/examples/parse_event_t.cpp new file mode 100644 index 000000000..3a56b22a9 --- /dev/null +++ b/docs/mkdocs/docs/examples/parse_event_t.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +using json = nlohmann::json; + +// translate a parse_event_t to a human-readable name +std::string event_name(json::parse_event_t event) +{ + switch (event) + { + case json::parse_event_t::object_start: + return "object_start"; + case json::parse_event_t::object_end: + return "object_end"; + case json::parse_event_t::array_start: + return "array_start"; + case json::parse_event_t::array_end: + return "array_end"; + case json::parse_event_t::key: + return "key"; + case json::parse_event_t::value: + return "value"; + default: + return "unknown"; + } +} + +int main() +{ + // a small JSON text + auto text = R"({"pi": 3.141, "numbers": [1, 2]})"; + + // parse the text and report every event together with its depth; + // returning true keeps every value unchanged + json j = json::parse(text, [](int depth, json::parse_event_t event, json& /*parsed*/) + { + std::cout << depth << " " << event_name(event) << '\n'; + return true; + }); + + // the callback did not change anything, so the parsed value is unaffected + std::cout << j << '\n'; +} diff --git a/docs/mkdocs/docs/examples/parse_event_t.output b/docs/mkdocs/docs/examples/parse_event_t.output new file mode 100644 index 000000000..f900d692f --- /dev/null +++ b/docs/mkdocs/docs/examples/parse_event_t.output @@ -0,0 +1,10 @@ +0 object_start +1 key +1 value +1 key +1 array_start +2 value +2 value +1 array_end +0 object_end +{"numbers":[1,2],"pi":3.141} diff --git a/docs/mkdocs/docs/examples/patch__exception.cpp b/docs/mkdocs/docs/examples/patch__exception.cpp new file mode 100644 index 000000000..9e0a27cab --- /dev/null +++ b/docs/mkdocs/docs/examples/patch__exception.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +using json = nlohmann::json; +using namespace nlohmann::literals; + +int main() +{ + // the original document + json doc = R"( + { + "a": { "b": 1 } + } + )"_json; + + // a patch that tries to move "/a" into one of its own children + json patch = R"( + [ + { "op": "move", "from": "/a", "path": "/a/b" } + ] + )"_json; + + // exception out_of_range.414 + try + { + json patched_doc = doc.patch(patch); + } + catch (const json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } + + // the original document is unchanged + std::cout << std::setw(4) << doc << std::endl; +} diff --git a/docs/mkdocs/docs/examples/patch__exception.output b/docs/mkdocs/docs/examples/patch__exception.output new file mode 100644 index 000000000..e6991f5f5 --- /dev/null +++ b/docs/mkdocs/docs/examples/patch__exception.output @@ -0,0 +1,6 @@ +[json.exception.out_of_range.414] cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/b' +{ + "a": { + "b": 1 + } +} diff --git a/docs/mkdocs/docs/examples/patch_inplace__exception.cpp b/docs/mkdocs/docs/examples/patch_inplace__exception.cpp new file mode 100644 index 000000000..02ebe133a --- /dev/null +++ b/docs/mkdocs/docs/examples/patch_inplace__exception.cpp @@ -0,0 +1,38 @@ +#include +#include +#include + +using json = nlohmann::json; +using namespace nlohmann::literals; + +int main() +{ + // the original document + json doc = R"( + { + "a": 1, + "b": 2 + } + )"_json; + + // a patch whose second operation fails + json patch = R"( + [ + { "op": "replace", "path": "/a", "value": 99 }, + { "op": "remove", "path": "/nonexistent" } + ] + )"_json; + + // exception out_of_range.403 + try + { + doc.patch_inplace(patch); + } + catch (const json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } + + // the first operation has already been applied to doc + std::cout << std::setw(4) << doc << std::endl; +} diff --git a/docs/mkdocs/docs/examples/patch_inplace__exception.output b/docs/mkdocs/docs/examples/patch_inplace__exception.output new file mode 100644 index 000000000..34a8c008f --- /dev/null +++ b/docs/mkdocs/docs/examples/patch_inplace__exception.output @@ -0,0 +1,5 @@ +[json.exception.out_of_range.403] key 'nonexistent' not found +{ + "a": 99, + "b": 2 +} diff --git a/docs/mkdocs/docs/examples/sax_no_exception.cpp b/docs/mkdocs/docs/examples/sax_no_exception.cpp new file mode 100644 index 000000000..1eeff466e --- /dev/null +++ b/docs/mkdocs/docs/examples/sax_no_exception.cpp @@ -0,0 +1,40 @@ +#include +#include + +using json = nlohmann::json; + +// a DOM parser that reports parse errors instead of throwing +class sax_no_exception : public nlohmann::detail::json_sax_dom_parser +{ + public: + explicit sax_no_exception(json& j) + : nlohmann::detail::json_sax_dom_parser(j, false) + {} + + bool parse_error(std::size_t position, + const std::string& last_token, + const json::exception& ex) + { + std::cout << "parse error at input byte " << position << "\n" + << ex.what() << "\n" + << "last read: \"" << last_token << "\"" + << std::endl; + return false; + } +}; + +int main() +{ + std::string myinput = "[1,2,3,]"; + + json result; + sax_no_exception sax(result); + + bool parse_result = json::sax_parse(myinput, &sax); + if (!parse_result) + { + std::cout << "parsing unsuccessful!" << std::endl; + } + + std::cout << "parsed value: " << result << std::endl; +} diff --git a/docs/mkdocs/docs/examples/sax_no_exception.output b/docs/mkdocs/docs/examples/sax_no_exception.output new file mode 100644 index 000000000..c2f4b78db --- /dev/null +++ b/docs/mkdocs/docs/examples/sax_no_exception.output @@ -0,0 +1,5 @@ +parse error at input byte 8 +[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal +last read: "3,]" +parsing unsuccessful! +parsed value: [1,2,3] diff --git a/docs/mkdocs/docs/examples/to_bjdata__exception.cpp b/docs/mkdocs/docs/examples/to_bjdata__exception.cpp new file mode 100644 index 000000000..560f3343b --- /dev/null +++ b/docs/mkdocs/docs/examples/to_bjdata__exception.cpp @@ -0,0 +1,20 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create a non-empty JSON array + json j = {1, 2, 3}; + + // exception other_error.502 + try + { + json::to_bjdata(j, false, true); + } + catch (const json::other_error& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/to_bjdata__exception.output b/docs/mkdocs/docs/examples/to_bjdata__exception.output new file mode 100644 index 000000000..53c5709fd --- /dev/null +++ b/docs/mkdocs/docs/examples/to_bjdata__exception.output @@ -0,0 +1 @@ +[json.exception.other_error.502] use_type requires use_size = true diff --git a/docs/mkdocs/docs/examples/to_bon8__exception.cpp b/docs/mkdocs/docs/examples/to_bon8__exception.cpp new file mode 100644 index 000000000..48992413b --- /dev/null +++ b/docs/mkdocs/docs/examples/to_bon8__exception.cpp @@ -0,0 +1,22 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON string that is not valid UTF-8 + std::string invalid_utf8; + invalid_utf8.push_back(static_cast(0xFF)); + json j = invalid_utf8; + + // exception type_error.316 + try + { + json::to_bon8(j); + } + catch (const json::type_error& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/to_bon8__exception.output b/docs/mkdocs/docs/examples/to_bon8__exception.output new file mode 100644 index 000000000..22fc8b14b --- /dev/null +++ b/docs/mkdocs/docs/examples/to_bon8__exception.output @@ -0,0 +1 @@ +[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF diff --git a/docs/mkdocs/docs/examples/to_bson__exception.cpp b/docs/mkdocs/docs/examples/to_bson__exception.cpp new file mode 100644 index 000000000..fe12c202f --- /dev/null +++ b/docs/mkdocs/docs/examples/to_bson__exception.cpp @@ -0,0 +1,23 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON object whose key contains a null byte (U+0000) + std::string key = "ab"; + key.push_back('\0'); + key.push_back('c'); + json j = {{key, 1}}; + + // exception out_of_range.409 + try + { + json::to_bson(j); + } + catch (const json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/to_bson__exception.output b/docs/mkdocs/docs/examples/to_bson__exception.output new file mode 100644 index 000000000..c5413f990 --- /dev/null +++ b/docs/mkdocs/docs/examples/to_bson__exception.output @@ -0,0 +1 @@ +[json.exception.out_of_range.409] BSON key cannot contain code point U+0000 (at byte 2) diff --git a/docs/mkdocs/docs/examples/to_msgpack__exception.cpp b/docs/mkdocs/docs/examples/to_msgpack__exception.cpp new file mode 100644 index 000000000..fb20d0ad8 --- /dev/null +++ b/docs/mkdocs/docs/examples/to_msgpack__exception.cpp @@ -0,0 +1,20 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value with a binary subtype that exceeds 255 + json j = json::binary({1, 2, 3}, 300); + + // exception out_of_range.415 + try + { + json::to_msgpack(j); + } + catch (const json::out_of_range& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/to_msgpack__exception.output b/docs/mkdocs/docs/examples/to_msgpack__exception.output new file mode 100644 index 000000000..c2ea36dba --- /dev/null +++ b/docs/mkdocs/docs/examples/to_msgpack__exception.output @@ -0,0 +1 @@ +[json.exception.out_of_range.415] subtype 300 is too large for the MessagePack ext type (max 255) diff --git a/docs/mkdocs/docs/examples/to_ubjson__exception.cpp b/docs/mkdocs/docs/examples/to_ubjson__exception.cpp new file mode 100644 index 000000000..8fe481d84 --- /dev/null +++ b/docs/mkdocs/docs/examples/to_ubjson__exception.cpp @@ -0,0 +1,20 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create a non-empty JSON array + json j = {1, 2, 3}; + + // exception other_error.502 + try + { + json::to_ubjson(j, false, true); + } + catch (const json::other_error& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/to_ubjson__exception.output b/docs/mkdocs/docs/examples/to_ubjson__exception.output new file mode 100644 index 000000000..53c5709fd --- /dev/null +++ b/docs/mkdocs/docs/examples/to_ubjson__exception.output @@ -0,0 +1 @@ +[json.exception.other_error.502] use_type requires use_size = true diff --git a/docs/mkdocs/docs/examples/value__exception.cpp b/docs/mkdocs/docs/examples/value__exception.cpp new file mode 100644 index 000000000..1d611d4c9 --- /dev/null +++ b/docs/mkdocs/docs/examples/value__exception.cpp @@ -0,0 +1,33 @@ +#include +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON object with a string value + json j = {{"name", "the good"}}; + + // exception type_error.302 + try + { + int v = j.value("name", 0); + std::cout << v << '\n'; + } + catch (const json::type_error& e) + { + std::cout << e.what() << '\n'; + } + + // exception type_error.306 + try + { + json str = "I am a string"; + auto v = str.value("name", 0); + std::cout << v << '\n'; + } + catch (const json::type_error& e) + { + std::cout << e.what() << '\n'; + } +} diff --git a/docs/mkdocs/docs/examples/value__exception.output b/docs/mkdocs/docs/examples/value__exception.output new file mode 100644 index 000000000..fab99f1f6 --- /dev/null +++ b/docs/mkdocs/docs/examples/value__exception.output @@ -0,0 +1,2 @@ +[json.exception.type_error.302] type must be number, but is string +[json.exception.type_error.306] cannot use value() with string diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md index 7ea01ee55..f7f3326ce 100644 --- a/docs/mkdocs/docs/features/arbitrary_types.md +++ b/docs/mkdocs/docs/features/arbitrary_types.md @@ -80,6 +80,29 @@ Some important things: * In function `from_json`, use function [`at()`](../api/basic_json/at.md) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. * You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these. +??? example "Example: serialize a `person` to JSON with `to_json`" + + ```cpp + --8<-- "examples/to_json.cpp" + ``` + + Output: + + ```json + --8<-- "examples/to_json.output" + ``` + +??? example "Example: deserialize a `person` from JSON with `from_json`" + + ```cpp + --8<-- "examples/from_json__default_constructible.cpp" + ``` + + Output: + + ``` + --8<-- "examples/from_json__default_constructible.output" + ``` ## Simplify your life with macros @@ -98,7 +121,29 @@ There are several macros to make your life easier as long as you want to use a J For all the macros, the first parameter is the name of the class/struct. The `DERIVED_TYPE` macros require a second parameter of a base class. All the remaining parameters name the member variables. The `WITH_NAMES` macros require a JSON name before each of the variables. -| Need access to private members | Need only de-serialization | Allow missing values when de-serializing | macro | +```mermaid +flowchart TD + A["choosing a NLOHMANN_DEFINE_* macro"] --> B{"adding fields to a base class?"} + B -->|"yes"| C["...DERIVED_TYPE..."] + B -->|"no"| D["...TYPE..."] + C --> E{"need access to private members?"} + D --> E + E -->|"yes"| F["...INTRUSIVE... (used inside the class)"] + E -->|"no"| G["...NON_INTRUSIVE... (used in the namespace)"] + F --> H{"only serializing, never parsing back?"} + G --> H + H -->|"yes"| I["...ONLY_SERIALIZE"] + H -->|"no"| J{"allow missing keys when parsing?"} + J -->|"yes"| K["...WITH_DEFAULT"] + J -->|"no"| L["plain (missing keys throw)"] + I --> M{"need custom JSON key names?"} + K --> M + L --> M + M -->|"yes"| N["...WITH_NAMES"] + M -->|"no"| O["done"] +``` + +| Need access to private members | Need only serialization | Allow missing values when de-serializing | macro | |------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_INTRUSIVE**](../api/macros/nlohmann_define_type_intrusive.md) | |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_type_intrusive.md) | @@ -109,7 +154,7 @@ For all the macros, the first parameter is the name of the class/struct. The `DE For _derived_ classes and structs, use the following macros -| Need access to private members | Need only de-serialization | Allow missing values when de-serializing | macro | +| Need access to private members | Need only serialization | Allow missing values when de-serializing | macro | |------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE**](../api/macros/nlohmann_define_derived_type.md) | |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_derived_type.md) | @@ -124,7 +169,7 @@ For _derived_ classes and structs, use the following macros types with more than 63 member variables, you need to define the `to_json`/`from_json` functions manually. - For the `WITH_NAMES` variants the limit is halved to 31 member variables. -??? example +??? example "Example: using the `NLOHMANN_DEFINE_TYPE_*` macros" The `to_json`/`from_json` functions for the `person` struct above can be created with: @@ -245,6 +290,14 @@ For _derived_ classes and structs, use the following macros This requires a bit more advanced technique. But first, let us see how this conversion mechanism works: +```mermaid +flowchart LR + A["construct json j = t, or call j.get() for T"] --> B["JSONSerializer for T: to_json / from_json"] + B -->|"default JSONSerializer"| C["adl_serializer for T: to_json / from_json"] + C -->|"unqualified call, found via ADL"| D["free to_json(j, t) / from_json(j, t) in T's namespace"] + B -->|"user specialization replaces the default"| E["user's adl_serializer specialization for T"] +``` + The library uses **JSON Serializers** to convert types to JSON. The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](https://en.cppreference.com/w/cpp/language/adl)). @@ -300,7 +353,24 @@ NLOHMANN_JSON_NAMESPACE_END ## How can I use `get()` for non-default constructible/non-copyable types? -There is a way if your type is [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible). You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload: +For a type that is not [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible) but is +otherwise an ordinary value type, specialize `adl_serializer` with a `from_json` overload that returns the value instead +of writing into a reference: + +??? example "Example: `get()` for a non-default-constructible type" + + ```cpp + --8<-- "examples/from_json__non_default_constructible.cpp" + ``` + + Output: + + ``` + --8<-- "examples/from_json__non_default_constructible.output" + ``` + +The same technique also works if your type is not copyable, as long as it is +[MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): ```cpp struct move_only_type { @@ -359,15 +429,10 @@ json any_to_json(const std::any& a) { ## Why does serializing a `std::map`/`std::unordered_map` with non-string keys produce an array? -A `std::map`/`std::unordered_map` whose key type is not string-like (e.g., `std::map`) is -serialized as a JSON *array* of 2-element `[key, value]` arrays, not as a JSON object -- JSON object keys must be -strings, so the library cannot represent an integer-keyed map as an object. - -```cpp -std::map m{{1, "one"}, {2, "two"}}; -json j = m; -// j is [[1,"one"],[2,"two"]], not {"1":"one","2":"two"} -``` +A `std::map`/`std::unordered_map` whose key type is not string-like (e.g., `std::map`) cannot be +serialized as a JSON object, because JSON object keys must be strings. See +[Converting maps with non-string keys](types/index.md#converting-maps-with-non-string-keys) in the types article for +what the library does instead. ## Why does `std::wstring` convert or dump incorrectly? @@ -411,7 +476,7 @@ struct less_than_32_serializer { Be **very** careful when reimplementing your serializer, you can stack overflow if you don't pay attention: ```cpp -template +template struct bad_serializer { template @@ -429,3 +494,10 @@ struct bad_serializer } }; ``` + +## See also + +- [Converting values](conversions.md) - the general overview of `get`/`get_to` and implicit conversions +- [Specializing enum conversion](enum_conversion.md) - map enums to JSON strings instead of integers +- [Supported macros](macros.md) - reference for `NLOHMANN_DEFINE_TYPE_*` and related macros +- [`adl_serializer`](../api/adl_serializer/index.md) - the default `JSONSerializer` used in the conversion dispatch diff --git a/docs/mkdocs/docs/features/assertions.md b/docs/mkdocs/docs/features/assertions.md index 789af7989..e0b850115 100644 --- a/docs/mkdocs/docs/features/assertions.md +++ b/docs/mkdocs/docs/features/assertions.md @@ -27,7 +27,7 @@ If you are not sure whether an element in an object exists, use checked access w See also the documentation on [element access](element_access/index.md). -??? example "Example 1: Missing object key" +??? example "Example: missing object key" The following code will trigger an assertion at runtime: @@ -54,7 +54,7 @@ See also the documentation on [element access](element_access/index.md). Constructing a JSON value from an iterator range (see [constructor](../api/basic_json/basic_json.md)) with an uninitialized iterator is undefined behavior and yields a runtime assertion. -??? example "Example 2: Uninitialized iterator range" +??? example "Example: uninitialized iterator range" The following code will trigger an assertion at runtime: @@ -81,7 +81,7 @@ uninitialized iterator is undefined behavior and yields a runtime assertion. Any operation on uninitialized iterators (i.e., iterators that are not associated with any JSON value) is undefined behavior and yields a runtime assertion. -??? example "Example 3: Uninitialized iterator" +??? example "Example: uninitialized iterator" The following code will trigger an assertion at runtime: @@ -112,7 +112,7 @@ library asserted that the pointer was not `nullptr` using a runtime assertion. I result in undefined behavior. Since version 3.12.0, this library checks for `nullptr` and throws a [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) to prevent the undefined behavior. -??? example "Example 4: Reading from null pointer" +??? example "Example: reading from null pointer" The following code will trigger an assertion at runtime: diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md index cd73b07e2..8e3ed41f6 100644 --- a/docs/mkdocs/docs/features/binary_formats/bjdata.md +++ b/docs/mkdocs/docs/features/binary_formats/bjdata.md @@ -73,7 +73,7 @@ The library uses the following mapping from JSON values types to BJData types ac !!! info "NaN/infinity handling" If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the - `dump()` function which serializes NaN or Infinity to `#!json null`. + [`dump()`](../../api/basic_json/dump.md) function which serializes NaN or Infinity to `#!json null`. !!! info "Endianness" @@ -163,7 +163,7 @@ The library uses the following mapping from JSON values types to BJData types ac [BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md#optimized-binary-array -??? example +??? example "Example: serialize JSON values to BJData, with and without size/type optimization" ```cpp --8<-- "examples/to_bjdata.cpp" @@ -218,7 +218,7 @@ The library maps BJData types to JSON value types as follows: binary values above), and serializing such an array again may choose different, but equally valid, type markers. The bytes can then differ, but parsing them again yields the same value. -??? example +??? example "Example: deserialize a JSON value from BJData" ```cpp --8<-- "examples/from_bjdata.cpp" diff --git a/docs/mkdocs/docs/features/binary_formats/bon8.md b/docs/mkdocs/docs/features/binary_formats/bon8.md index 9b5fa1dda..d2d20da19 100644 --- a/docs/mkdocs/docs/features/binary_formats/bon8.md +++ b/docs/mkdocs/docs/features/binary_formats/bon8.md @@ -53,8 +53,9 @@ The library uses the following mapping from JSON values types to BON8 types acco An integer that takes 2 to 4 bytes starts with a UTF-8 lead byte (0xC2..0xF7) that is followed by a byte that cannot continue a UTF-8 character: 0x00..0x7F for positive and 0xC0..0xFF for negative integers. A string is terminated by -0xFF only if it is empty, if another string follows it, or if it is the last value of the message; otherwise, the first -byte of the next value ends it. +0xFF only if it is empty, if another string follows it, or if nothing follows it in the message; otherwise, the byte +after it ends it: the first byte of the next value, or the 0xFE that ends an array or object. For example, `["e"]` is +serialized as 0x81 0x65 0xFF, but `[1,2,3,4,"e"]` as 0x85 0x91 0x92 0x93 0x94 0x65 0xFE. !!! success "Complete mapping" @@ -92,7 +93,7 @@ byte of the next value ends it. - Object keys are written in the order of the object type, which is sorted for `json`, but not for [`ordered_json`](../../api/ordered_json.md). -??? example +??? example "Example: serialize a JSON value to BON8" ```cpp --8<-- "examples/to_bon8.cpp" @@ -140,13 +141,13 @@ Non-negative integers are read as number_unsigned, negative integers as number_i arrays and objects with up to four elements that are terminated by 0xFE, unsorted object keys, or a 0xFF after a string that would also end without it, are accepted. A second 0xFF is not a terminator but an empty string. - Strings must be valid UTF-8, and the last string of a message must be terminated by 0xFF. + Strings must be valid UTF-8, and a string at the very end of a message must be terminated by 0xFF. !!! info Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`. -??? example +??? example "Example: deserialize a JSON value from BON8" ```cpp --8<-- "examples/from_bon8.cpp" diff --git a/docs/mkdocs/docs/features/binary_formats/bson.md b/docs/mkdocs/docs/features/binary_formats/bson.md index 6f5603c8c..cca11451e 100644 --- a/docs/mkdocs/docs/features/binary_formats/bson.md +++ b/docs/mkdocs/docs/features/binary_formats/bson.md @@ -48,7 +48,7 @@ The library uses the following mapping from JSON values types to BSON types: As a result, serializing and deserializing a JSON object containing such a value produces a different JSON object, even though the binary data is unchanged. -??? example +??? example "Example: serialize a JSON value to BSON" ```cpp --8<-- "examples/to_bson.cpp" @@ -118,7 +118,7 @@ The library maps BSON record types to JSON value types as follows: (key) names and `binary` values (type `0x05`) are unaffected and are never validated, since they are read byte-by-byte as a C string, or are not required to hold text, respectively. -??? example +??? example "Example: deserialize a JSON value from BSON" ```cpp --8<-- "examples/from_bson.cpp" diff --git a/docs/mkdocs/docs/features/binary_formats/cbor.md b/docs/mkdocs/docs/features/binary_formats/cbor.md index a488466d4..eb7bc7f41 100644 --- a/docs/mkdocs/docs/features/binary_formats/cbor.md +++ b/docs/mkdocs/docs/features/binary_formats/cbor.md @@ -98,7 +98,7 @@ see "binary" cells in the table above. Binary subtypes will be serialized as tagged items. See [binary values](../binary_values.md#cbor) for an example. -??? example +??? example "Example: serialize a JSON value to CBOR" ```cpp --8<-- "examples/to_cbor.cpp" @@ -203,7 +203,7 @@ The library maps CBOR types to JSON value types as follows: Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string. -??? example +??? example "Example: deserialize a JSON value from CBOR" ```cpp --8<-- "examples/from_cbor.cpp" diff --git a/docs/mkdocs/docs/features/binary_formats/messagepack.md b/docs/mkdocs/docs/features/binary_formats/messagepack.md index 3ce5f7620..2e674252a 100644 --- a/docs/mkdocs/docs/features/binary_formats/messagepack.md +++ b/docs/mkdocs/docs/features/binary_formats/messagepack.md @@ -79,7 +79,7 @@ specification: total), because the check used to select the smaller float 32 encoding compared magnitudes with NaN, which is always `false` and caused the float 32 path to be skipped. -??? example +??? example "Example: serialize a JSON value to MessagePack" ```cpp --8<-- "examples/to_msgpack.cpp" @@ -162,7 +162,7 @@ The library maps MessagePack types to JSON value types as follows: value is dumped. `bin`/`ext`/`fixext` values are unaffected and are never validated, since they are not required to hold text. -??? example +??? example "Example: deserialize a JSON value from MessagePack" ```cpp --8<-- "examples/from_msgpack.cpp" diff --git a/docs/mkdocs/docs/features/binary_formats/ubjson.md b/docs/mkdocs/docs/features/binary_formats/ubjson.md index be545b9fe..37aa069e2 100644 --- a/docs/mkdocs/docs/features/binary_formats/ubjson.md +++ b/docs/mkdocs/docs/features/binary_formats/ubjson.md @@ -11,29 +11,29 @@ achieve the generality of JSON, combined with being much easier to process than The library uses the following mapping from JSON values types to UBJSON types according to the UBJSON specification: -| JSON value type | value/range | UBJSON type | marker | -|-----------------|-----------------------------------|----------------|--------| -| null | `null` | null | `Z` | -| boolean | `true` | true | `T` | -| boolean | `false` | false | `F` | -| number_integer | -9223372036854775808..-2147483649 | int64 | `L` | -| number_integer | -2147483648..-32769 | int32 | `l` | -| number_integer | -32768..-129 | int16 | `I` | -| number_integer | -128..127 | int8 | `i` | -| number_integer | 128..255 | uint8 | `U` | -| number_integer | 256..32767 | int16 | `I` | -| number_integer | 32768..2147483647 | int32 | `l` | -| number_integer | 2147483648..9223372036854775807 | int64 | `L` | -| number_unsigned | 0..127 | int8 | `i` | -| number_unsigned | 128..255 | uint8 | `U` | -| number_unsigned | 256..32767 | int16 | `I` | -| number_unsigned | 32768..2147483647 | int32 | `l` | -| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` | -| number_unsigned | 2147483649..18446744073709551615 | high-precision | `H` | -| number_float | *any value* | float64 | `D` | -| string | *with shortest length indicator* | string | `S` | -| array | *see notes on optimized format* | array | `[` | -| object | *see notes on optimized format* | map | `{` | +| JSON value type | value/range | UBJSON type | marker | +|-----------------|-------------------------------------------|----------------|--------| +| null | `null` | null | `Z` | +| boolean | `true` | true | `T` | +| boolean | `false` | false | `F` | +| number_integer | -9223372036854775808..-2147483649 | int64 | `L` | +| number_integer | -2147483648..-32769 | int32 | `l` | +| number_integer | -32768..-129 | int16 | `I` | +| number_integer | -128..127 | int8 | `i` | +| number_integer | 128..255 | uint8 | `U` | +| number_integer | 256..32767 | int16 | `I` | +| number_integer | 32768..2147483647 | int32 | `l` | +| number_integer | 2147483648..9223372036854775807 | int64 | `L` | +| number_unsigned | 0..127 | int8 | `i` | +| number_unsigned | 128..255 | uint8 | `U` | +| number_unsigned | 256..32767 | int16 | `I` | +| number_unsigned | 32768..2147483647 | int32 | `l` | +| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` | +| number_unsigned | 9223372036854775808..18446744073709551615 | high-precision | `H` | +| number_float | *any value* | float64 | `D` | +| string | *with shortest length indicator* | string | `S` | +| array | *see notes on optimized format* | array | `[` | +| object | *see notes on optimized format* | map | `{` | !!! success "Complete mapping" @@ -57,7 +57,7 @@ The library uses the following mapping from JSON values types to UBJSON types ac !!! info "NaN/infinity handling" If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the - `dump()` function which serializes NaN or Infinity to `null`. + [`dump()`](../../api/basic_json/dump.md) function which serializes NaN or Infinity to `null`. !!! info "Optimized formats" @@ -82,7 +82,7 @@ The library uses the following mapping from JSON values types to UBJSON types ac documentation. In particular, this means that serialization and the deserialization of a JSON containing binary values into UBJSON and back will result in a different JSON object. -??? example +??? example "Example: serialize JSON values to UBJSON, with and without size/type optimization" ```cpp --8<-- "examples/to_ubjson.cpp" @@ -120,7 +120,7 @@ The library maps UBJSON types to JSON value types as follows: The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value. -??? example +??? example "Example: deserialize a JSON value from UBJSON" ```cpp --8<-- "examples/from_ubjson.cpp" diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index aa3185c2a..98e8e71c3 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -27,7 +27,7 @@ vector <|-- binary_t By default, binary values are stored as `std::vector`. This type can be changed by providing a template parameter to the `basic_json` type. To store binary subtypes, the storage type is extended and exposed as -`json::binary_t`: +[`json::binary_t`](../api/basic_json/binary_t.md): ```cpp auto binary = json::binary_t({0xCA, 0xFE, 0xBA, 0xBE}); @@ -62,21 +62,23 @@ JSON values can be constructed from `json::binary_t`: json j = binary; ``` -Binary values are primitive values just like numbers or strings: +Binary values are primitive values just like numbers or strings, as reflected by +[`is_binary()`](../api/basic_json/is_binary.md) and [`is_primitive()`](../api/basic_json/is_primitive.md): ```cpp j.is_binary(); // returns true j.is_primitive(); // returns true ``` -Given a binary JSON value, the `binary_t` can be accessed by reference as via `get_binary()`: +Given a binary JSON value, the `binary_t` can be accessed by reference via +[`get_binary()`](../api/basic_json/get_binary.md): ```cpp j.get_binary().has_subtype(); // returns true j.get_binary().size(); // returns 4 ``` -For convenience, binary JSON values can be constructed via `json::binary`: +For convenience, binary JSON values can be constructed via [`json::binary`](../api/basic_json/binary.md): ```cpp auto j2 = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 23); @@ -99,7 +101,7 @@ JSON does not have a binary type, and this library does not introduce a new type Instead, binary values are serialized as an object with two keys: `bytes` holds an array of integers, and `subtype` is an integer or `null`. -??? example +??? example "Example: serialize a binary value to JSON" Code: @@ -133,7 +135,7 @@ is an integer or `null`. [BJData](binary_formats/bjdata.md) neither supports binary values nor subtypes and proposes to serialize binary values as an array of uint8 values. The library implements this translation. -??? example +??? example "Example: serialize a binary value to BJData" Code: @@ -192,7 +194,7 @@ as an array of uint8 values. The library implements this translation. [BON8](binary_formats/bon8.md) neither supports binary values nor subtypes. The library serializes binary values as an array of integers. -??? example +??? example "Example: serialize a binary value to BON8" Code: @@ -227,7 +229,7 @@ array of integers. [BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as an unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00 is used. -??? example +??? example "Example: serialize a binary value to BSON" Code: @@ -269,7 +271,7 @@ unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00 value will be serialized as byte strings. The library will choose the smallest representation using the length of the byte array. -??? example +??? example "Example: serialize a binary value to CBOR" Code: @@ -294,7 +296,9 @@ byte array. ``` Note that the subtype is serialized as tag. However, parsing tagged values yield a parse error unless - `json::cbor_tag_handler_t::ignore` or `json::cbor_tag_handler_t::store` is passed to `json::from_cbor`. + `json::cbor_tag_handler_t::ignore` or `json::cbor_tag_handler_t::store` is passed to + [`json::from_cbor`](../api/basic_json/from_cbor.md) (see + [`cbor_tag_handler_t`](../api/basic_json/cbor_tag_handler_t.md)). ```json { @@ -313,7 +317,7 @@ ext32. The subtype is then added as a signed 8-bit integer. If no subtype is given, the bin family (bin8, bin16, bin32) is used. -??? example +??? example "Example: serialize a binary value to MessagePack" Code: @@ -353,7 +357,7 @@ If no subtype is given, the bin family (bin8, bin16, bin32) is used. [UBJSON](binary_formats/ubjson.md) neither supports binary values nor subtypes and proposes to serialize binary values as an array of uint8 values. The library implements this translation. -??? example +??? example "Example: serialize a binary value to UBJSON" Code: diff --git a/docs/mkdocs/docs/features/comments.md b/docs/mkdocs/docs/features/comments.md index 95ac72359..86321bc4a 100644 --- a/docs/mkdocs/docs/features/comments.md +++ b/docs/mkdocs/docs/features/comments.md @@ -11,7 +11,7 @@ This library does not support comments *by default*. It does so for three reason 3. It is dangerous for interoperability if some libraries add comment support while others do not. Please check [The Harmful Consequences of the Robustness Principle](https://tools.ietf.org/html/draft-iab-protocol-maintenance-01) on this. -However, you can set parameter `ignore_comments` to `#!cpp true` in the [`parse`](../api/basic_json/parse.md) function to ignore `//` or `/* */` comments. Comments will then be treated as whitespace. Combined with `ignore_trailing_commas` (also a `parse` parameter), this covers what is commonly referred to as **JSONC** (JSON with Comments, as used e.g. by Visual Studio Code's `.jsonc` files) -- comments and trailing commas, nothing more. This is a different, smaller extension than [JSON5](https://json5.org), which additionally allows unquoted keys, single-quoted strings, and other syntax changes that this library does not support. +However, you can set parameter `ignore_comments` to `#!cpp true` in the [`parse`](../api/basic_json/parse.md) function to ignore `//` or `/* */` comments. Comments will then be treated as whitespace. Combined with [`ignore_trailing_commas`](trailing_commas.md) (also a `parse` parameter), this covers what is commonly referred to as **JSONC** (JSON with Comments, as used e.g. by Visual Studio Code's `.jsonc` files) -- comments and trailing commas, nothing more. This is a different, smaller extension than [JSON5](https://json5.org), which additionally allows unquoted keys, single-quoted strings, and other syntax changes that this library does not support. For more information, see [JSON With Commas and Comments (JWCC)](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html). diff --git a/docs/mkdocs/docs/features/element_access/checked_access.md b/docs/mkdocs/docs/features/element_access/checked_access.md index 1fb65e53b..d8dab0104 100644 --- a/docs/mkdocs/docs/features/element_access/checked_access.md +++ b/docs/mkdocs/docs/features/element_access/checked_access.md @@ -6,7 +6,7 @@ The [`at`](../../api/basic_json/at.md) member function performs checked access; desired value if it exists and throws a [`basic_json::out_of_range` exception](../../home/exceptions.md#out-of-range) otherwise. -??? example "Read access" +??? example "Example: read access" Consider the following JSON value: @@ -31,7 +31,7 @@ otherwise. The return value is a reference, so it can be used to modify the original value. -??? example "Write access" +??? example "Example: write access" ```cpp j.at("name") = "John Smith"; @@ -50,7 +50,7 @@ The return value is a reference, so it can be used to modify the original value. When accessing an invalid index (i.e., an index greater than or equal to the array size) or the passed object key is non-existing, an exception is thrown. -??? example "Accessing via invalid index or missing key" +??? example "Example: access via invalid index or missing key" ```cpp j.at("hobbies").at(3) = "cooking"; diff --git a/docs/mkdocs/docs/features/element_access/default_value.md b/docs/mkdocs/docs/features/element_access/default_value.md index 7b613062b..481448469 100644 --- a/docs/mkdocs/docs/features/element_access/default_value.md +++ b/docs/mkdocs/docs/features/element_access/default_value.md @@ -41,9 +41,9 @@ you want to access and a default value in case there is no value stored with tha The value function is a template, and the return type of the function is determined by the type of the provided default value unless otherwise specified. This can have unexpected effects. In the example below, we store a 64-bit - unsigned integer. We get exactly that value when using [`operator[]`](../../api/basic_json/operator[].md). However, - when we call `value` and provide `#!c 0` as default value, then `#!c -1` is returned. This occurs, because `#!c 0` - has type `#!c int` which overflows when handling the value `#!c 18446744073709551615`. + unsigned integer. We get exactly that value when using [`operator[]`](../../api/basic_json/operator%5B%5D.md). + However, when we call `value` and provide `#!c 0` as default value, then `#!c -1` is returned. This occurs, + because `#!c 0` has type `#!c int` which overflows when handling the value `#!c 18446744073709551615`. To address this issue, either provide a correctly typed default value or use the template parameter to specify the desired return type. Note that this issue occurs even when a value is stored at the provided key, and the default diff --git a/docs/mkdocs/docs/features/element_access/index.md b/docs/mkdocs/docs/features/element_access/index.md index 0b39547ec..262c057b8 100644 --- a/docs/mkdocs/docs/features/element_access/index.md +++ b/docs/mkdocs/docs/features/element_access/index.md @@ -5,5 +5,19 @@ There are many ways elements in a JSON value can be accessed: - unchecked access via [`operator[]`](unchecked_access.md) - checked access via [`at`](checked_access.md) - access with default value via [`value`](default_value.md) -- iterators -- JSON pointers +- [iterators](../iterators.md) +- [JSON pointers](../json_pointer.md) + +Testing whether a key or index exists before accessing it is also possible, with +[`contains`](../../api/basic_json/contains.md) or [`find`](../../api/basic_json/find.md) (which returns an iterator to +the value, or `end()` if it is not found). + +```mermaid +flowchart TD + A["accessing a value"] --> B{"must it exist?"} + B -->|"yes, missing is an error"| C["at() -- throws"] + B -->|"yes, but checking is my job"| D["operator[] -- unchecked"] + B -->|"no, a fallback is fine"| E["value() -- default value"] + A --> F{"just testing first?"} + F -->|"yes"| G["contains() / find()"] +``` diff --git a/docs/mkdocs/docs/features/element_access/unchecked_access.md b/docs/mkdocs/docs/features/element_access/unchecked_access.md index edaaa37a3..7e3f92ba1 100644 --- a/docs/mkdocs/docs/features/element_access/unchecked_access.md +++ b/docs/mkdocs/docs/features/element_access/unchecked_access.md @@ -5,7 +5,7 @@ Elements in a JSON object and a JSON array can be accessed via [`operator[]`](../../api/basic_json/operator%5B%5D.md) similar to a `#!cpp std::map` and a `#!cpp std::vector`, respectively. -??? example "Read access" +??? example "Example: read access" Consider the following JSON value: @@ -31,7 +31,7 @@ similar to a `#!cpp std::map` and a `#!cpp std::vector`, respectively. The return value is a reference, so it can modify the original value. In case the passed object key is non-existing, a `#!json null` value is inserted which can immediately be overwritten. -??? example "Write access" +??? example "Example: write access" ```cpp j["name"] = "John Smith"; @@ -52,7 +52,7 @@ The return value is a reference, so it can modify the original value. In case th When accessing an invalid index (i.e., an index greater than or equal to the array size), the JSON array is resized such that the passed index is the new maximal index. Intermediate values are filled with `#!json null`. -??? example "Filling up arrays with `#!json null` values" +??? example "Example: filling up arrays with `#!json null` values" ```cpp j["hobbies"][0] = "running"; @@ -94,8 +94,8 @@ that the passed index is the new maximal index. Intermediate values are filled w - It is **undefined behavior** to access a const object with a non-existing key. - It is **undefined behavior** to access a const array with an invalid index. - In debug mode, an **assertion** will fire in both cases. You can disable assertions by defining the preprocessor - symbol `#!cpp NDEBUG` or redefine the macro [`JSON_ASSERT(x)`](../macros.md#json_assertx). See the documentation - on [runtime assertions](../assertions.md) for more information. + symbol `#!cpp NDEBUG` or redefine the macro [`JSON_ASSERT(x)`](../../api/macros/json_assert.md). See the + documentation on [runtime assertions](../assertions.md) for more information. !!! failure "Exceptions" @@ -105,8 +105,9 @@ that the passed index is the new maximal index. Intermediate values are filled w ## Performance: reserving array capacity There is no public `reserve(count)` member on `basic_json` for pre-allocating array capacity. If you are building -a large array incrementally (e.g., via repeated `push_back()`) and know its final size ahead of time, you can -reserve capacity via `get_ref()` to access the underlying `array_t` directly: +a large array incrementally (e.g., via repeated [`push_back()`](../../api/basic_json/push_back.md)) and know its final +size ahead of time, you can reserve capacity via [`get_ref()`](../../api/basic_json/get_ref.md) to access the +underlying `array_t` directly: ```cpp json j = json::array(); diff --git a/docs/mkdocs/docs/features/enum_conversion.md b/docs/mkdocs/docs/features/enum_conversion.md index d75d6e112..3efd8e818 100644 --- a/docs/mkdocs/docs/features/enum_conversion.md +++ b/docs/mkdocs/docs/features/enum_conversion.md @@ -29,6 +29,9 @@ The [`NLOHMANN_JSON_SERIALIZE_ENUM()` macro](../api/macros/nlohmann_json_seriali ## Usage +Serialization converts an enum value to its mapped string, deserialization does the reverse, and an unrecognized JSON +value deserializes to the first pair in the map: + ```cpp // enum to JSON as string json j = TS_STOPPED; @@ -43,6 +46,18 @@ json jPi = 3.14; assert(jPi.get() == TS_INVALID ); ``` +??? example "Example: serializing/deserializing enums, including a second enum type" + + ```cpp + --8<-- "examples/nlohmann_json_serialize_enum.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_json_serialize_enum.output" + ``` + ## Notes Just as in [Arbitrary Type Conversions](arbitrary_types.md) above, @@ -54,9 +69,25 @@ Just as in [Arbitrary Type Conversions](arbitrary_types.md) above, Other Important points: -- When using `get()`, undefined JSON values will default to the first pair specified in your map. Select this - default pair carefully. If you desire an exception in this circumstance use [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT()`](../api/macros/nlohmann_json_serialize_enum_strict.md) - which behaves identically except for throwing an exception on unrecognized values. +- When using [`get()`](../api/basic_json/get.md), undefined JSON values will default to the first pair + specified in your map. Select this default pair carefully. If you desire an exception in this circumstance use + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT()`](../api/macros/nlohmann_json_serialize_enum_strict.md) which behaves + identically except for throwing an + [`out_of_range.410`](../home/exceptions.md#jsonexceptionout_of_range410) exception on unrecognized values, both when + serializing an enum value not listed in the map and when deserializing a JSON value that matches none of the map's + entries. - If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the map will be returned when converting to or from JSON. - To disable the default serialization of enumerators as integers and force a compiler error instead, see [`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). + +??? example "Example: `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` throwing on unrecognized values" + + ```cpp + --8<-- "examples/nlohmann_json_serialize_enum_strict_err.cpp" + ``` + + Output: + + ```json + --8<-- "examples/nlohmann_json_serialize_enum_strict_err.output" + ``` diff --git a/docs/mkdocs/docs/features/index.md b/docs/mkdocs/docs/features/index.md index 33403a58c..aaf1253a7 100644 --- a/docs/mkdocs/docs/features/index.md +++ b/docs/mkdocs/docs/features/index.md @@ -10,7 +10,8 @@ C++ types, and finally serialize it again. understand the `#!cpp {}` vs. `#!cpp []` ambiguity. - [Parsing](parsing/index.md) — read a JSON value from a string, file, or stream, including [JSON Lines](parsing/json_lines.md), [callbacks](parsing/parser_callbacks.md), the - [SAX interface](parsing/sax_interface.md), and [error handling](parsing/parse_exceptions.md). + [SAX interface](parsing/sax_interface.md), [error handling](parsing/parse_exceptions.md), and + [parsing untrusted input](parsing/untrusted_input.md). - [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar. ## Accessing and modifying values @@ -43,7 +44,10 @@ C++ types, and finally serialize it again. - [Types](types/index.md) and [number handling](types/number_handling.md) — how JSON types map to C++ types and how numbers are treated. +- [Template parameter requirements](types/template_parameters.md) — what a type passed as one of `basic_json`'s + template parameters has to provide. - [Object order](object_order.md) — keep insertion order with [`ordered_json`](../api/ordered_json.md). +- [Performance](performance.md) — practical advice on parsing, memory use, serialization, and compile times. - [Runtime assertions](assertions.md), [supported macros](macros.md), the [`nlohmann` namespace](namespace.md), and [C++ modules](modules.md) — build-time and runtime configuration. diff --git a/docs/mkdocs/docs/features/iterators.md b/docs/mkdocs/docs/features/iterators.md index f45b92fdc..de493dd72 100644 --- a/docs/mkdocs/docs/features/iterators.md +++ b/docs/mkdocs/docs/features/iterators.md @@ -4,7 +4,10 @@ A `basic_json` value is a container and allows access via iterators. Depending on the value type, `basic_json` stores zero or more values. -As for other containers, `begin()` returns an iterator to the first value and `end()` returns an iterator to the value following the last value. The latter iterator is a placeholder and cannot be dereferenced. In case of null values, empty arrays, or empty objects, `begin()` will return `end()`. +As for other containers, [`begin()`](../api/basic_json/begin.md) returns an iterator to the first value and +[`end()`](../api/basic_json/end.md) returns an iterator to the value following the last value. The latter iterator is a +placeholder and cannot be dereferenced. In case of null values, empty arrays, or empty objects, `begin()` will return +`end()`. ![Illustration from cppreference.com](../images/range-begin-end.svg) @@ -12,7 +15,7 @@ As for other containers, `begin()` returns an iterator to the first value and `e When iterating over objects, values are ordered with respect to the `object_comparator_t` type which defaults to `std::less`. See the [types documentation](types/index.md#key-order) for more information. -??? example +??? example "Example: iteration order of object values" ```cpp // create JSON object {"one": 1, "two": 2, "three": 3} @@ -41,7 +44,7 @@ When iterating over objects, values are ordered with respect to the `object_comp The JSON iterators have two member functions, `key()` and `value()` to access the object key and stored value, respectively. When calling `key()` on a non-object iterator, an [invalid_iterator.207](../home/exceptions.md#jsonexceptioninvalid_iterator207) exception is thrown. -??? example +??? example "Example: access object keys with `key()` and `value()`" ```cpp // create JSON object {"one": 1, "two": 2, "three": 3} @@ -76,7 +79,9 @@ for (auto it : j_object) } ``` -For this reason, the `items()` function allows accessing `iterator::key()` and `iterator::value()` during range-based for loops. In these loops, a reference to the JSON values is returned, so there is no access to the underlying iterator. +For this reason, the [`items()`](../api/basic_json/items.md) function allows accessing `iterator::key()` and +`iterator::value()` during range-based for loops. In these loops, a reference to the JSON values is returned, so there +is no access to the underlying iterator. ```cpp for (auto& el : j_object.items()) @@ -104,11 +109,12 @@ for (auto& [key, val] : j_object.items()) ### Reverse iteration order -`rbegin()` and `rend()` return iterators in the reverse sequence. +[`rbegin()`](../api/basic_json/rbegin.md) and [`rend()`](../api/basic_json/rend.md) return iterators in the reverse +sequence. ![Illustration from cppreference.com](../images/range-rbegin-rend.svg) -??? example +??? example "Example: reverse iteration with `rbegin()` and `rend()`" ```cpp json j = {1, 2, 3, 4}; @@ -132,7 +138,7 @@ for (auto& [key, val] : j_object.items()) Note that "value" means a JSON value in this setting, not values stored in the underlying containers. That is, `*begin()` returns the complete string or binary array and is also safe if the underlying string or binary array is empty. -??? example +??? example "Example: iterate over a string value" ```cpp json j = "Hello, world"; diff --git a/docs/mkdocs/docs/features/json_patch.md b/docs/mkdocs/docs/features/json_patch.md index 835f07f90..878f0084c 100644 --- a/docs/mkdocs/docs/features/json_patch.md +++ b/docs/mkdocs/docs/features/json_patch.md @@ -3,10 +3,17 @@ ## Patches JSON Patch ([RFC 6902](https://tools.ietf.org/html/rfc6902)) defines a JSON document structure for expressing a sequence -of operations to apply to a JSON document. With the `patch` function, a JSON Patch is applied to the current JSON value -by executing all operations from the patch. +of operations to apply to a JSON document. Operations address locations in the document using +[JSON Pointer](json_pointer.md) paths. With the [`patch`](../api/basic_json/patch.md) function, a JSON Patch is applied +to the current JSON value by executing all operations from the patch, yielding the patched document as a new value. -??? example +!!! tip "Applying a patch without copying" + + [`patch`](../api/basic_json/patch.md) leaves the original value unchanged and returns the patched result as a copy. + If the document is large and the original value is no longer needed, + [`patch_inplace`](../api/basic_json/patch_inplace.md) applies the same operations in place instead. + +??? example "Example: apply a JSON Patch" The following code shows how a JSON patch is applied to a value. @@ -22,7 +29,15 @@ by executing all operations from the patch. ## Diff -The library can also calculate a JSON patch (i.e., a **diff**) given two JSON values. +The library can also calculate a JSON patch (i.e., a **diff**) given two JSON values with the +[`diff`](../api/basic_json/diff.md) function. + +```mermaid +flowchart LR + S["source"] -->|"diff(source, target)"| P["patch"] + S -->|"source.patch(patch)"| T["target"] + P -.->|"applied to source, yields"| T +``` !!! success "Invariant" @@ -32,7 +47,7 @@ The library can also calculate a JSON patch (i.e., a **diff**) given two JSON va source.patch(diff(source, target)) == target; ``` -??? example +??? example "Example: create a JSON Patch from the difference of two values" The following code shows how a JSON patch is created as a diff for two JSON values. @@ -45,3 +60,11 @@ The library can also calculate a JSON patch (i.e., a **diff**) given two JSON va ```json --8<-- "examples/diff.output" ``` + +## See also + +- [JSON Pointer](json_pointer.md) - the addressing scheme used for patch paths +- [JSON Merge Patch](merge_patch.md) - a simpler, less expressive alternative patch format +- [`patch`](../api/basic_json/patch.md) - apply a JSON Patch, returning the result as a copy +- [`patch_inplace`](../api/basic_json/patch_inplace.md) - apply a JSON Patch without copying +- [`diff`](../api/basic_json/diff.md) - compute a JSON Patch from two values diff --git a/docs/mkdocs/docs/features/json_pointer.md b/docs/mkdocs/docs/features/json_pointer.md index c7237c266..786832c96 100644 --- a/docs/mkdocs/docs/features/json_pointer.md +++ b/docs/mkdocs/docs/features/json_pointer.md @@ -128,4 +128,5 @@ auto j_original = j_flat.unflatten(); - Class [`json_pointer`](../api/json_pointer/index.md) - Function [`flatten`](../api/basic_json/flatten.md) - Function [`unflatten`](../api/basic_json/unflatten.md) -- [JSON Patch](json_patch.md) +- [JSON Patch](json_patch.md) - paths inside a patch are JSON Pointers +- [JSON Merge Patch](merge_patch.md) - an alternative patch format that does not use JSON Pointer diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index a4479712f..90bb352c3 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -179,7 +179,8 @@ See [full documentation of `JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global ## `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON` When defined to `1`, the library restores the legacy behavior in which a discarded value compared equal to itself. This -behavior is deprecated and switched off (`0`) by default. +behavior is [deprecated](../integration/migration_guide.md#miscellaneous-functions) and switched off (`0`) by +default. See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md). diff --git a/docs/mkdocs/docs/features/merge_patch.md b/docs/mkdocs/docs/features/merge_patch.md index 84e0ab02f..46b0c5f04 100644 --- a/docs/mkdocs/docs/features/merge_patch.md +++ b/docs/mkdocs/docs/features/merge_patch.md @@ -1,9 +1,13 @@ # JSON Merge Patch The library supports JSON Merge Patch ([RFC 7386](https://tools.ietf.org/html/rfc7386)) as a patch format. -The merge patch format is primarily intended for use with the HTTP PATCH method as a means of describing a set of modifications to a target resource's content. This function applies a merge patch to the current JSON value. +The merge patch format is primarily intended for use with the HTTP PATCH method as a means of describing a set of +modifications to a target resource's content. This function applies a merge patch to the current JSON value. -Instead of using [JSON Pointer](json_pointer.md) to specify values to be manipulated, it describes the changes using a syntax that closely mimics the document being modified. +Instead of using [JSON Pointer](json_pointer.md) to specify values to be manipulated, it describes the changes using a +syntax that closely mimics the document being modified. Unlike [JSON Patch](json_patch.md), a JSON Merge Patch cannot +express every kind of change (e.g., it cannot reorder array elements or remove a specific array element), but it is +easier to read and write for object-shaped documents. ??? example @@ -18,3 +22,9 @@ Instead of using [JSON Pointer](json_pointer.md) to specify values to be manipul ```json --8<-- "examples/merge_patch.output" ``` + +## See also + +- [JSON Patch and Diff](json_patch.md) - a more expressive alternative that describes a sequence of operations +- [JSON Pointer](json_pointer.md) - the addressing scheme used by JSON Patch +- Function [`merge_patch`](../api/basic_json/merge_patch.md) diff --git a/docs/mkdocs/docs/features/object_order.md b/docs/mkdocs/docs/features/object_order.md index 200913fd2..b90637711 100644 --- a/docs/mkdocs/docs/features/object_order.md +++ b/docs/mkdocs/docs/features/object_order.md @@ -6,7 +6,7 @@ The [JSON standard](https://tools.ietf.org/html/rfc8259.html) defines objects as The default type `nlohmann::json` uses a `std::map` to store JSON objects, and thus stores object keys **sorted alphabetically**. -??? example +??? example "Example: `json` sorts object keys" ```cpp #include @@ -39,7 +39,7 @@ The default type `nlohmann::json` uses a `std::map` to store JSON objects, and t If you do want to preserve the **insertion order**, you can use the type [`nlohmann::ordered_json`](../api/ordered_json.md). -??? example +??? example "Example: `ordered_json` preserves insertion order" ```cpp --8<-- "examples/ordered_json.cpp" diff --git a/docs/mkdocs/docs/features/parsing/index.md b/docs/mkdocs/docs/features/parsing/index.md index 476f024fa..9e5371d95 100644 --- a/docs/mkdocs/docs/features/parsing/index.md +++ b/docs/mkdocs/docs/features/parsing/index.md @@ -3,6 +3,16 @@ This library can create a JSON value from a wide range of inputs. This page gives an overview of the available parsing functions and how they behave; the linked pages go into more detail. +```mermaid +flowchart LR + I["JSON input"] --> P["parse()"] + I --> S["sax_parse()"] + I --> A["accept()"] + P -->|"optional parser callback filters values"| D["basic_json value (DOM)"] + S --> H["events delivered to a user SAX handler"] + A --> V["bool: is the input valid JSON?"] +``` + ## Input The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be @@ -76,3 +86,4 @@ options. - [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function - [SAX interface](sax_interface.md) - implement a custom SAX handler - [parsing and exceptions](parse_exceptions.md) - control error handling +- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources diff --git a/docs/mkdocs/docs/features/parsing/json_lines.md b/docs/mkdocs/docs/features/parsing/json_lines.md index fb1481819..5ecf9d4af 100644 --- a/docs/mkdocs/docs/features/parsing/json_lines.md +++ b/docs/mkdocs/docs/features/parsing/json_lines.md @@ -46,8 +46,20 @@ JSON Lines input with more than one value is treated as invalid JSON by the [`pa } ``` - with a JSON Lines input does not work, because the parser will try to parse one value after the last one. + with a JSON Lines input does not work, because the parser will try to parse one value after the last one and throw + a [`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) exception. The same happens for a + stream of *concatenated* (non-newline-delimited) JSON values: `operator>>` reads them one at a time, but the loop + above throws after the last value. To read either format with `operator>>`, check for the end of the stream before + each read: - This is different from parsing a stream of *concatenated* (non-newline-delimited) JSON values, for which - `operator>>` does work, provided that a value that is a number is followed by whitespace -- see its - [notes](../../api/operator_gtgt.md#notes) for details. + ```cpp + json j; + while (input >> std::ws && input.peek() != std::char_traits::eof()) + { + input >> j; + std::cout << j << std::endl; + } + ``` + + A value that is a number must be followed by whitespace -- see the [notes](../../api/operator_gtgt.md#notes) of + `operator>>` for details. diff --git a/docs/mkdocs/docs/features/parsing/parse_exceptions.md b/docs/mkdocs/docs/features/parsing/parse_exceptions.md index 25b4768ff..f524b67d8 100644 --- a/docs/mkdocs/docs/features/parsing/parse_exceptions.md +++ b/docs/mkdocs/docs/features/parsing/parse_exceptions.md @@ -23,9 +23,9 @@ In case exceptions are undesired or not supported by the environment, there are ## Switch off exceptions -The `parse()` function accepts a `#!cpp bool` parameter `allow_exceptions` which controls whether an exception is -thrown when a parse error occurs (`#!cpp true`, default) or whether a discarded value should be returned -(`#!cpp false`). +The [`parse()`](../../api/basic_json/parse.md) function accepts a `#!cpp bool` parameter `allow_exceptions` which +controls whether an exception is thrown when a parse error occurs (`#!cpp true`, default) or whether a discarded value +should be returned (`#!cpp false`). ```cpp json j = json::parse(my_input, nullptr, false); @@ -39,8 +39,8 @@ Note there is no diagnostic information available in this scenario. ## Use accept() function -Alternatively, function `accept()` can be used which does not return a `json` value, but a `#!cpp bool` indicating -whether the input is valid JSON. +Alternatively, function [`accept()`](../../api/basic_json/accept.md) can be used which does not return a `json` value, +but a `#!cpp bool` indicating whether the input is valid JSON. ```cpp if (!json::accept(my_input)) @@ -66,56 +66,18 @@ bool parse_error(std::size_t position, The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`. -??? example +??? example "Example: report parse errors without exceptions" + + The example derives from the library's DOM parser and overrides `parse_error` to print the error instead of + throwing. Note the DOM parser is an implementation detail (`nlohmann::detail`) and may change between releases; + see [Do not use the `detail` namespace](../../integration/migration_guide.md#do-not-use-the-detail-namespace). ```cpp - #include - #include - - using json = nlohmann::json; - - class sax_no_exception : public nlohmann::detail::json_sax_dom_parser - { - public: - sax_no_exception(json& j) - : nlohmann::detail::json_sax_dom_parser(j, false) - {} - - bool parse_error(std::size_t position, - const std::string& last_token, - const json::exception& ex) - { - std::cerr << "parse error at input byte " << position << "\n" - << ex.what() << "\n" - << "last read: \"" << last_token << "\"" - << std::endl; - return false; - } - }; - - int main() - { - std::string myinput = "[1,2,3,]"; - - json result; - sax_no_exception sax(result); - - bool parse_result = json::sax_parse(myinput, &sax); - if (!parse_result) - { - std::cerr << "parsing unsuccessful!" << std::endl; - } - - std::cout << "parsed value: " << result << std::endl; - } + --8<-- "examples/sax_no_exception.cpp" ``` Output: - + ``` - parse error at input byte 8 - [json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal - last read: "3,]" - parsing unsuccessful! - parsed value: [1,2,3] + --8<-- "examples/sax_no_exception.output" ``` diff --git a/docs/mkdocs/docs/features/parsing/parser_callbacks.md b/docs/mkdocs/docs/features/parsing/parser_callbacks.md index e65ac0eb9..6144011ec 100644 --- a/docs/mkdocs/docs/features/parsing/parser_callbacks.md +++ b/docs/mkdocs/docs/features/parsing/parser_callbacks.md @@ -2,8 +2,9 @@ ## Overview -With a parser callback function, the result of parsing a JSON text can be influenced. When passed to `parse`, it is -called on certain events (passed as `parse_event_t` via parameter `event`) with a set recursion depth `depth` and +With a parser callback function, the result of parsing a JSON text can be influenced. When passed to +[`parse`](../../api/basic_json/parse.md), it is called on certain events (passed as +[`parse_event_t`](../../api/basic_json/parse_event_t.md) via parameter `event`) with a set recursion depth `depth` and context JSON value `parsed`. The return value of the callback function is a boolean indicating whether the element that emitted the callback shall be kept or not. @@ -30,7 +31,7 @@ table describes the values of the parameters `depth`, `event`, and `parsed`. | `parse_event_t::array_end` | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array | | `parse_event_t::value` | the parser finished reading a JSON value | depth of the value | the parsed JSON value | -??? example +??? example "Example: sequence of callback events" When parsing the following JSON text, @@ -76,7 +77,7 @@ was called: - In case a value outside a structured type is skipped, it is replaced with `#!json null`. This case happens if the top-level element is skipped. -??? example +??? example "Example: skip an object key while parsing" The example below demonstrates the `parse()` function with and without callback function. @@ -98,7 +99,7 @@ the resulting `#!c json` value -- once parsing has produced that value, the dupl storage maps each key to a single value. If duplicate keys should instead be treated as an error, a parser callback can detect them while the object is still being read, before that ambiguity ever applies. -??? example +??? example "Example: reject duplicate object keys" ```cpp --8<-- "examples/reject_duplicate_keys.cpp" @@ -110,16 +111,18 @@ can detect them while the object is still being read, before that ambiguity ever --8<-- "examples/reject_duplicate_keys.output" ``` -This approach has two limitations: +This approach has three limitations: - The depth-indexed bookkeeping must account for the fact that `object_start` reports the depth of the *parent* of the object, while the `key` events inside that object are reported one depth deeper (see the event table above); it is easy to get this off by one for nested objects. - The thrown exception cannot carry a `parse_error`-style byte offset, because position tracking only exists inside the parser and lexer, not at the callback layer. +- The exception only names the repeated key, not where it occurs in the document. Reporting its full path requires + maintaining a stack of the enclosing keys and array indices in the callback as well. -For strict validation with precise error positions, implementing a [SAX interface](sax_interface.md) instead gives -access to the parser's position information directly. +A [SAX interface](sax_interface.md) does not lift the position limitation: its `key` function receives no position +either -- only `parse_error` is passed the byte position. ## Recipe: streaming a large homogeneous array @@ -129,7 +132,7 @@ discard it, so memory usage stays bounded by a single element (plus the not-yet- than the whole document. Since the top-level array's `array_start`/`array_end` are reported at `depth == 0` (its parent is the document root), the object elements it contains are reported at `depth == 1`: -??? example +??? example "Example: stream a large top-level array" ```cpp std::ifstream input("large_array.json"); @@ -154,7 +157,7 @@ homogeneous values by checking `object_end`/`value` events at `depth == 1` there Since there is no built-in nesting-depth limit (see the note above), a callback can enforce one manually by tracking the maximum `depth` seen and throwing once it is exceeded: -??? example +??? example "Example: limit the nesting depth" ```cpp constexpr int max_depth = 32; diff --git a/docs/mkdocs/docs/features/parsing/untrusted_input.md b/docs/mkdocs/docs/features/parsing/untrusted_input.md new file mode 100644 index 000000000..d39a4179e --- /dev/null +++ b/docs/mkdocs/docs/features/parsing/untrusted_input.md @@ -0,0 +1,163 @@ +# Parsing Untrusted Input + +This page is for applications that parse JSON -- or one of the supported [binary formats](../binary_formats/index.md) +(BJData, BON8, BSON, CBOR, MessagePack, UBJSON) -- from a source they do not fully control, such as a network +connection, an uploaded file, or another process. It summarizes what the library already does for such input and what +remains the caller's responsibility, linking to the pages that cover each aspect in detail rather than repeating them. + +For the project's threat model and the countermeasures behind these behaviors, see the +[assurance case](../../community/assurance_case.md); to report a vulnerability, see the +[security policy](../../community/security_policy.md). + +## Errors without exceptions + +By default, [`parse()`](../../api/basic_json/parse.md) throws a +[`parse_error`](../../home/exceptions.md#jsonexceptionparse_error101) (for instance `parse_error.101` for a syntax +error) when the input is not valid. If your environment cannot use exceptions for untrusted input, the library offers +several alternatives; see [Parsing and exceptions](parse_exceptions.md) for the full comparison: + +- Pass `#!cpp false` as the third argument to `parse()` to get a discarded value + (checked with [`is_discarded()`](../../api/basic_json/is_discarded.md)) instead of a thrown exception, with no + diagnostic information. +- Use [`accept()`](../../api/basic_json/accept.md) to only check whether the input is valid JSON, without building a + value. +- Implement the [SAX interface](sax_interface.md) and override `parse_error()` to react to an error yourself, with the + byte position and the exception that would otherwise have been thrown; see the + [example](parse_exceptions.md#user-defined-sax-interface) that overrides it to print instead of throw. + +If exceptions are unavailable entirely (`-fno-exceptions`, or [`JSON_NOEXCEPTION`](../../api/macros/json_noexception.md) +defined), every `#!cpp throw` in the library becomes a call to `std::abort()` -- there is no way to recover from a +parse error of untrusted input in that configuration; see +[Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for the details and for overriding this with +`JSON_THROW_USER`. + +## Nesting depth + +The JSON parser and the binary readers are iterative: they keep the containers they are currently inside of on a +heap-allocated stack instead of calling themselves once per nesting level, so the native call stack does not grow with +the nesting depth of the input. A deeply nested document is therefore bounded by available memory, not by the call +stack, however deeply it is nested. + +!!! warning "No built-in depth limit while parsing" + + Neither the parser nor the binary readers impose a limit on how deep the input may nest. An attacker can still + exhaust memory (though not the call stack) with a sufficiently deep document. If you need to reject over-deep + untrusted input outright, track the depth yourself, either with a + [parser callback](parser_callbacks.md#recipe-max-nesting-depth-via-a-callback) for the JSON parser, or by counting + `start_object`/`start_array` and `end_object`/`end_array` calls in a + [SAX handler](sax_interface.md) (for the JSON parser or a binary format alike) and throwing once your limit is + exceeded. + +Once a value has been parsed, operations that walk it recursively -- serializing it with +[`dump`](../../api/basic_json/dump.md), hashing it, copying it, comparing two values with `#!cpp ==`, `#!cpp <`, or (in +C++20) `#!cpp <=>`, merging with [`update`](../../api/basic_json/update.md), and applying a +[`merge_patch`](../../api/basic_json/merge_patch.md) -- descend at most 128 levels on the call stack and continue +below that with an explicit stack instead, so none of them can exhaust the stack either, however deeply the value is +nested. Destroying a value (its destructor) never recurses at all, regardless of nesting depth, for the same reason. + +!!! note "Not every operation is bounded yet" + + [`diff`](../../api/basic_json/diff.md), [`flatten`](../../api/basic_json/flatten.md), and the binary writers + (`to_cbor`, `to_msgpack`, ...) still recurse once per nesting level; this is called out as work in progress in the + [assurance case](../../community/assurance_case.md#secure-design). A value deep enough to matter for these + operations would typically first have to survive parsing without hitting a self-imposed depth limit, as described + above. + +## Input size + +The library does not limit the overall size of a JSON text; a value nested or wide enough will use memory +proportional to the input. If you parse untrusted input of unbounded size, check the size of the file or stream +yourself before -- or while -- handing it to `parse()`. + +For the binary formats, an announced size is never trusted outright: + +- Reading a string or binary value copies the input in bounded 4096-byte chunks and grows the result as bytes are + actually consumed, rather than allocating the announced length up front -- a truncated input runs out of bytes + (reported as a parse error) instead of triggering an oversized allocation. +- When an array announces its number of elements and the array container supports `reserve()` (as `#!cpp std::vector`, + the default, does), the library reserves storage for at most 16384 of them upfront, regardless of how large the + announced count is; further elements still grow the container normally as they are read. +- An announced array or object size that exceeds what the target container could ever hold (its `max_size()`) is + rejected immediately as [`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408), without + attempting to allocate anything. + +## Strings + +Invalid UTF-8 is rejected while parsing, not just while serializing: + +- In JSON text, an ill-formed UTF-8 byte in a string is a + [`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) ("invalid string: ill-formed UTF-8 byte"). +- In a binary format, a string that is not valid UTF-8 is a + [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113). + +A `#!cpp '\0'` (NUL) byte *inside* a quoted JSON string is always rejected (it must be escaped as `\u0000`). A NUL byte +*outside* of a string is different: by default it is silently treated as the end of the input, so trailing bytes after +it -- including further, otherwise well-formed JSON -- are silently ignored rather than rejected. Since untrusted input +that happens to embed a NUL is a way to make part of it disappear without a parse error, see the +[FAQ entry](../../home/faq.md#nul-bytes-in-the-input) and consider defining +[`JSON_STRICT_NUL_HANDLING`](../../api/macros/json_strict_nul_handling.md) to `1` to reject a NUL byte like any other +unexpected byte instead. + +Parsing is not the only place invalid UTF-8 matters: a string that reached a `#!cpp json` value some other way (for +example, constructed by application code, or, before JSON_STRICT_NUL_HANDLING existed, read from a binary format that +does not validate strings) still has to round-trip back to JSON text. By default, +[`dump()`](../../api/basic_json/dump.md) throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) +if the string is not valid UTF-8; passing +[`error_handler_t::replace`](../../api/basic_json/error_handler_t.md) or `error_handler_t::ignore` avoids the exception +instead of crashing an application that forgot to catch it. See +[Handling invalid UTF-8](../serialization.md#handling-invalid-utf-8) for the options and an example. + +## Duplicate object keys + +The JSON specification leaves the handling of repeated keys in an object up to the implementation, and this library +does too: as described in [`object_t`](../../api/basic_json/object_t.md#behavior), it is unspecified which of the +values for a repeated key ends up in the parsed object. If your application must reject duplicate keys instead of +silently resolving them one way or another, see the +[parser callback recipe for rejecting duplicate keys](parser_callbacks.md#recipe-rejecting-duplicate-object-keys). + +## Numbers + +A number whose value cannot be represented -- for instance `1E1000`, which overflows `double` -- is rejected while +parsing as [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406) rather than silently becoming +infinity. An integer that is syntactically valid but does not fit the 64-bit integer types is not rejected; it is +instead stored as a `double`, which may lose precision for very large values. See +[number limits](../types/number_handling.md#number-limits) for the exact ranges and an example. + +## Comments and trailing commas + +Both [comments](../comments.md) and [trailing commas](../trailing_commas.md) are rejected by default, matching the +JSON specification; they must be explicitly enabled per call with the `ignore_comments` and `ignore_trailing_commas` +parameters of [`parse()`](../../api/basic_json/parse.md) or [`accept()`](../../api/basic_json/accept.md). Do not +enable either for input whose conformance you cannot otherwise control, since interoperability with strictly +conforming JSON consumers is exactly what the default rejects. + +## Checklist + +- Wrap parsing in a `#!cpp try`/`#!cpp catch` block, or use `allow_exceptions=false`/`accept()` if your environment + cannot use exceptions; never let `JSON_NOEXCEPTION`'s `abort()` be the first time you think about error handling. +- If the input's nesting depth matters to you, enforce your own limit with a + [parser callback](parser_callbacks.md#recipe-max-nesting-depth-via-a-callback) or a + [SAX handler](sax_interface.md); the library bounds the call stack but not memory use. +- Bound the size of the input itself before parsing, independent of the library's own bounded allocations for binary + format lengths. +- Decide up front how you want a string with invalid UTF-8 -- from any source, not only parsing -- to be serialized + (`strict`, `replace`, or `ignore`), rather than discovering it from an uncaught `type_error.316`. +- If a stray NUL byte silently truncating trailing input is a problem for your input format, define + `JSON_STRICT_NUL_HANDLING`. +- Decide whether duplicate object keys should be an error for your application, and add a callback if so. +- Do not enable `ignore_comments` or `ignore_trailing_commas` for input that must be strictly conforming JSON. + +For the broader design rationale and how it is tested (fuzzing, sanitizers, static analysis), see the +[assurance case](../../community/assurance_case.md) and [quality assurance](../../community/quality_assurance.md). To +report a security issue in the library itself, follow the [security policy](../../community/security_policy.md). + +## See also + +- [Parsing](index.md) - overview of the parsing functions +- [Parsing and exceptions](parse_exceptions.md) - error handling without exceptions +- [Parser callbacks](parser_callbacks.md) - depth limits, duplicate-key rejection, and streaming recipes +- [SAX interface](sax_interface.md) - implement a custom handler with access to parse errors and positions +- [Serialization](../serialization.md) - handling invalid UTF-8 when dumping +- [Number handling](../types/number_handling.md) - number ranges and overflow behavior +- [Assurance case](../../community/assurance_case.md) - the library's threat model and countermeasures +- [Security policy](../../community/security_policy.md) - how to report a vulnerability diff --git a/docs/mkdocs/docs/features/performance.md b/docs/mkdocs/docs/features/performance.md new file mode 100644 index 000000000..d6e9f70fd --- /dev/null +++ b/docs/mkdocs/docs/features/performance.md @@ -0,0 +1,215 @@ +# Performance + +Speed was never the primary goal of this library. The [design goals](../home/design_goals.md) page says so plainly: +"There are certainly faster JSON libraries out there." Intuitive syntax, trivial integration, and thorough testing came +first. If a hard real-time budget or the last percent of throughput matters more than convenience, a +[faster, more specialized library](https://github.com/miloyip/nativejson-benchmark#parsing-time) may be a better fit. + +That said, how you use this library still makes a measurable difference. This page collects practical, code-verified +techniques for reducing time, memory, and compile-time cost -- without repeating the detailed pages it links to. + +## Parsing input + +[`parse`](../api/basic_json/parse.md) accepts a string, a pair of iterators, a container, a `#!cpp std::istream`, or a +`#!cpp FILE*` (see [Parsing](parsing/index.md#input)). Internally, every input is wrapped in an +[input adapter](../home/architecture.md#input-adapters), and not all adapters are equally fast. + +For inputs backed by contiguous, single-byte memory -- a `#!cpp std::string`, a `#!cpp std::vector`, a string +literal, or a pointer range -- the library uses `iterator_input_adapter`, wrapped in a raw pointer so the fast paths +below apply on every supported standard. This adapter exposes two optimizations the lexer detects at compile time: + +- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is + read, and +- the lexer can scan ordinary string characters directly out of the buffer, several bytes at a time, rather than one + character (and one function call) at a time. + +A `#!cpp std::istream` (including `#!cpp std::ifstream`) or `#!cpp FILE*`, by contrast, is read through +`input_stream_adapter` or `file_input_adapter`, which read one character (or one block, for binary formats) at a time +and expose neither optimization -- the lexer falls back to the same byte-at-a-time path it uses for any +non-contiguous, general-purpose iterator range. An iterator pair over non-contiguous but random-access storage (e.g. +`#!cpp std::deque::iterator`) gets the first optimization but not the second, since the byte-scanning fast path +additionally requires contiguous storage. + +Practically: if the JSON text is already in memory, or small enough to read into memory, prefer passing a +`#!cpp std::string`, a `#!cpp std::vector`, or a pointer range to `parse` over a `#!cpp std::istream`. For a +file, that means reading it into a string first and then parsing the string, rather than passing a +`#!cpp std::ifstream` directly to `parse` -- the latter never benefits from either optimization: + +```cpp +// gets the contiguous fast paths +std::ifstream f("example.json"); +std::string contents((std::istreambuf_iterator(f)), std::istreambuf_iterator()); +json j = json::parse(contents); + +// does not: input_stream_adapter has no fast path +std::ifstream f2("example.json"); +json j2 = json::parse(f2); +``` + +For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can +additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of +the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters) +always use the scalar path regardless of this macro. + +## Large documents + +Parsing always produces SAX events internally; [`parse`](../api/basic_json/parse.md) simply feeds them to a consumer +that builds a complete `basic_json` value tree (a DOM) in memory. For documents too large to comfortably hold as +a DOM, two alternatives avoid building it: + +- Implement the [SAX interface](parsing/sax_interface.md) directly and pass it to + [`sax_parse`](../api/basic_json/sax_parse.md); only the parts of the input you choose to keep ever become + `basic_json` values. +- Pass a [parser callback](parsing/parser_callbacks.md) to `parse`. This still builds a DOM, but the callback can + discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the + unparsed remainder of the input) instead of the whole document -- see the [recipe for streaming a large homogeneous + array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array). + +If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON +document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory +at a time, and a malformed line does not invalidate lines already processed. + +## Binary formats + +JSON text is not a compact format. If the data is only exchanged between programs (not read by humans), the +[binary formats](binary_formats/index.md) -- BJData, BON8, BSON, CBOR, MessagePack, and UBJSON -- encode the same +values more compactly, which reduces both the bytes transferred and, for most of them, the work needed to parse them +back. The [size comparison](binary_formats/index.md#sizes) on that page, measured against minified JSON for four +reference documents, shows the effect varies a lot by document shape: CBOR and MessagePack come out at 50.5% of the +minified JSON size for the numeric-array-heavy `canada.json`, but only around 87-88% for the string-heavy +`jeopardy.json`, where there is less numeric data to encode more compactly. BON8 is the most compact option in that +comparison for text-heavy documents (63.5%-87.5%), at the cost of an +[incomplete serializer](binary_formats/index.md#completeness) (no unsigned integers above int64). Which format -- and +whether it is worth the loss of human readability at all -- depends on the actual data; see the +[comparison tables](binary_formats/index.md#comparison) before choosing one. + +## Object type: `json` vs. `ordered_json` + +The default [`json`](../api/json.md) type stores object keys in a `#!cpp std::map`, giving logarithmic-time lookup, +insertion, and erasure, at the cost of sorting keys alphabetically rather than preserving insertion order (see +[Object Order](object_order.md)). [`ordered_json`](../api/ordered_json.md) uses +[`nlohmann::ordered_map`](../api/ordered_map.md) instead, a `#!cpp std::vector`-backed container with no lookup index: +every key-based operation is a **linear scan**, so building an object of `n` distinct keys costs **O(n²)** in total -- +this applies equally to inserting keys one by one and to parsing an object, since the parser inserts each key as it is +read. The [measurements on the `ordered_map` page](../api/ordered_map.md#complexity) show this is +negligible at typical object sizes (2000 keys: 0.7 ms for `json` vs. 3.6 ms for `ordered_json`, a 5x factor) but grows +steeply for large, machine-generated objects (16 000 keys: 3.3 ms vs. 181.6 ms, a 54x factor). + +If insertion order matters *and* an object routinely has many thousands of keys, `ordered_json`'s quadratic build cost +may not be acceptable. The library's [`ObjectType` template parameter](types/template_parameters.md#objecttype) can be +set to a different container instead: `#!cpp nlohmann::fifo_map` keeps insertion order with a real lookup index +(avoiding the quadratic cost), while `#!cpp std::unordered_map`, `#!cpp boost::unordered_flat_map`, +`#!cpp absl::flat_hash_map`, and similar hash maps trade insertion order for average-case constant-time lookup (through +an adapter, since their template argument order does not match what `basic_json` expects) -- see +[Object Order](object_order.md#alternative-behavior-preserve-insertion-order) for the full list. + +## Avoiding copies + +- **Move instead of copy.** Constructing a `basic_json` from an existing one is + [linear in its size](../api/basic_json/basic_json.md#complexity) for the copy constructor but + [constant](../api/basic_json/basic_json.md#complexity) for the move constructor. The same applies to assigning a + large `#!cpp std::string`, `#!cpp std::vector`, or other container into a value: pass it as `#!cpp std::move(x)` + rather than `x` whenever `x` is no longer needed afterwards. +- **Access without copying.** [`get()`](../api/basic_json/get.md) returns a copy of the stored value converted to + `T`. When a reference or pointer to the value already stored inside the `basic_json` is enough, + [`get_ref()`](../api/basic_json/get_ref.md) and [`get_ptr()`](../api/basic_json/get_ptr.md) access it directly: + both pages state, word for word, "No copies are made." -- at the cost of that reference or pointer becoming invalid + once the underlying value changes. +- **Iterate by reference.** `#!cpp basic_json::iterator::operator*()` returns a `reference` (an alias for + `#!cpp basic_json&`), but a range-based for loop with a by-value loop variable (`#!cpp for (auto el : j)`) still + copies each element, because plain `#!cpp auto` drops the reference. Write `#!cpp for (const auto& el : j)` (or + `#!cpp auto&` for a mutable loop), and use [`items()`](../api/basic_json/items.md) the same way when the key is + needed too -- its own examples use `#!cpp for (auto& el : j.items())`. +- **Construct in place.** [`emplace_back()`](../api/basic_json/emplace_back.md) (arrays, amortized constant time) and + [`emplace()`](../api/basic_json/emplace.md) (objects, logarithmic in the size of the container for `json`) forward + their arguments directly to a `basic_json` constructor, rather than requiring a temporary value to be + constructed and then copied or moved in. [`push_back()`](../api/basic_json/push_back.md) has an rvalue overload + (`#!cpp push_back(basic_json&&)`) for a value that already exists: `#!cpp j.push_back(std::move(value))` moves it + in instead of copying it. +- **Skip the bounds check when it is redundant.** [`at()`](../api/basic_json/at.md) and + [`operator[]`](../api/basic_json/operator%5B%5D.md) have the same complexity (constant for a valid array index, + logarithmic for an object key in `json`) -- the difference is that `at()` additionally checks the key or index and + throws if it is invalid, while `operator[]` does not (see [unchecked access](element_access/unchecked_access.md) and + [checked access](element_access/checked_access.md)). Prefer `operator[]` when the surrounding code has already + established that the access is valid. +- **Reserve array capacity.** `basic_json` has no public `reserve()`, but when building a large array + incrementally with a known final size, [`get_ref()`](../api/basic_json/get_ref.md) exposes the underlying + `#!cpp array_t` so it can be reserved directly -- see + ["reserving array capacity"](element_access/unchecked_access.md#performance-reserving-array-capacity) for the + one-line recipe. + +## Serialization + +[`dump()`](../api/basic_json/dump.md) with the default `#!cpp indent = -1` selects "the most compact representation" +(word for word from the page); any non-negative `indent` pretty-prints instead, which is more readable but produces +more bytes and more work. `dump()` builds and returns a complete `#!cpp string_t` containing the whole serialization. +[`operator<<`](../api/operator_ltlt.md) writes directly to a `#!cpp std::ostream` instead, through the same +serializer, but without ever materializing that intermediate string -- so if the destination is a stream (a file, or +`#!cpp std::cout`), `#!cpp os << j;` avoids the allocation and copy that `#!cpp os << j.dump();` would incur for large +values. + +## Diagnostics overhead + +Two opt-in macros add diagnostic information to exceptions and to every value, at a cost that is only worth paying +while it is in use: + +- [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) adds a JSON Pointer to exception messages, pointing at the + value that triggered the exception. Quoting the page directly: "enabling this macro increases the size of every + JSON value by one pointer and adds some runtime overhead" -- every value gains a parent pointer that has to be kept + up to date as the document is built and modified. +- [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) adds + [`start_pos()`](../api/basic_json/start_pos.md) and [`end_pos()`](../api/basic_json/end_pos.md), the byte offsets a + value occupied in its parsed input. Quoting the page: "enabling this macro increases the size of every JSON value by + two `std::size_t` fields and adds slight runtime overhead to parsing, copying JSON value objects, and the generation + of error messages for exceptions." + +Both default to off. Enable them where better diagnostics are worth the overhead (for example, while validating +untrusted input, or in a debug build), and keep them off in a release build that does not need them. + +## Compile time + +[``](../home/architecture.md#source-layout) forward-declares +[`basic_json`](../api/basic_json/index.md), [`json`](../api/json.md), [`ordered_json`](../api/ordered_json.md), +[`json_pointer`](../api/json_pointer/index.md), and [`adl_serializer`](../api/adl_serializer/index.md), pulling in only +a handful of lightweight standard headers instead of the full `json.hpp`. A header that only needs to *name* +`nlohmann::json` -- in a function signature or a class member declaration, for instance -- can include `json_fwd.hpp` +and leave `#!cpp #include ` to the source files that actually parse, build, or serialize values, +the same way a project would forward-declare any other heavy class to keep it out of widely-included headers: + +```cpp +// my_type.hpp +#include + +class my_type +{ + nlohmann::json config() const; +}; + +// my_type.cpp +#include +#include "my_type.hpp" + +nlohmann::json my_type::config() const { /* ... */ } +``` + +One caveat: ABI-affecting macros such as `JSON_DIAGNOSTICS` and `JSON_DIAGNOSTIC_POSITIONS` are encoded into the +library's [inline namespace name](namespace.md#limitations). Every translation unit -- whether it includes +`json_fwd.hpp` or the full header -- must define them the same way, or linking fails with undefined references +instead of a compile error. + +If I/O support is not needed at all, [`JSON_NO_IO`](../api/macros/json_no_io.md) excludes ``, ``, +``, ``, and `` outright and drops the `#!cpp std::istream`/`#!cpp FILE*` `parse` overloads and +[`operator<<`](../api/operator_ltlt.md) that depend on them (`dump()` itself is unaffected, since it only returns a +string); it exists for environments where those headers are unavailable (such as Intel SGX), and as a side effect +those headers are then never processed by the compiler at all. + +## See also + +- [Design goals](../home/design_goals.md) - why this library does not optimize for speed first +- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together +- [Parsing](parsing/index.md) - the available parsing functions and inputs +- [SAX interface](parsing/sax_interface.md) - parse without building a DOM +- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text +- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices +- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types +- [Supported macros](macros.md) - overview of all configuration macros, including the diagnostics ones above diff --git a/docs/mkdocs/docs/features/serialization.md b/docs/mkdocs/docs/features/serialization.md index 5b916e6c8..d9802bf08 100644 --- a/docs/mkdocs/docs/features/serialization.md +++ b/docs/mkdocs/docs/features/serialization.md @@ -28,7 +28,7 @@ std::cout << j << std::endl; By default, `dump` produces the most compact representation without any superfluous whitespace. Passing a non-negative `indent` argument pretty-prints the output with the given number of spaces per level: -??? example +??? example "Example: pretty-print JSON values with `dump()`" ```cpp --8<-- "examples/dump.cpp" @@ -65,7 +65,7 @@ serialization fails by default. The fourth argument of `dump` selects an - `replace` — replace invalid bytes with the Unicode replacement character U+FFFD (`ļæ½`). - `ignore` — silently drop invalid bytes. -??? example +??? example "Example: serialize invalid UTF-8 with different error handlers" ```cpp --8<-- "examples/error_handler_t.cpp" diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index ef43054e5..bae315160 100644 --- a/docs/mkdocs/docs/features/types/number_handling.md +++ b/docs/mkdocs/docs/features/types/number_handling.md @@ -67,14 +67,29 @@ Positive integers are stored as `#!c std::uint64_t`, while negative integers are distinction is determined at parse time: if the JSON number has a leading minus sign, it uses signed integer storage; otherwise, it uses unsigned integer storage. +```mermaid +flowchart TD + A["number literal"] --> B{"has a fraction (.) or exponent (e/E)?"} + B -->|"yes"| F["number_float_t"] + B -->|"no"| C{"has a leading minus sign?"} + C -->|"yes"| D["try number_integer_t"] + C -->|"no"| E["try number_unsigned_t"] + D -->|"overflow"| F + E -->|"overflow"| F +``` + !!! info "Notes" - Numbers with a decimal digit or scientific notation are always stored as `#!c double`. - The number types can be changed, see [Template number types](#template-number-types). - - As of version 3.9.1, the conversion is realized by + - Integers are converted by the library's own digit parser. Floating-point numbers are converted with + [`std::from_chars`](https://en.cppreference.com/w/cpp/utility/from_chars) if the library is compiled with C++17 + and the standard library supports it, then with an exact fast path for `#!c double` values with few significant + digits, and otherwise with the locale-aware + [`std::strtod`](https://en.cppreference.com/w/cpp/string/byte/strtof) (`std::strtof`/`std::strtold` for the + other floating-point types). Before version 3.13.0, the conversion was realized by [`std::strtoull`](https://en.cppreference.com/w/cpp/string/byte/strtoul), - [`std::strtoll`](https://en.cppreference.com/w/cpp/string/byte/strtol), and - [`std::strtod`](https://en.cppreference.com/w/cpp/string/byte/strtof), respectively. + [`std::strtoll`](https://en.cppreference.com/w/cpp/string/byte/strtol), and `std::strtod`, respectively. !!! example "Examples" diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index 5b528fd79..a83b7a56a 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -26,8 +26,9 @@ Requirements are split into two groups: diagnosed with dedicated error messages, and violating most of them results in a compiler error somewhere inside the library. Four violations are not caught at compile time at all: - - A [`StringType`](#stringtype) whose `data()` is not null-terminated compiles and silently misparses numbers, - because the lexer hands the buffer to `#!cpp std::strtoull`/`#!cpp std::strtoll`/`#!cpp std::strtod`. + - A [`StringType`](#stringtype) whose `data()` is not null-terminated compiles and can silently misparse + floating-point numbers, because the lexer may hand the buffer to `#!cpp std::strtod`, which reads up to the + terminating null character. - A stateful [`AllocatorType`](#allocatortype) compiles and silently ignores its state: allocation, deallocation, and [`get_allocator()`](../../api/basic_json/get_allocator.md) each use a different default-constructed instance. - The two [cross-specialization conversions](#cross-specialization-conversions) below. These abort on an assertion @@ -213,7 +214,7 @@ The library does not sort or de-duplicate keys itself; the behavior described in --8<-- "examples/custom_object_type.hpp" ``` -??? example "Compiling and using it" +??? example "Example: use the custom `ObjectType`" ```cpp --8<-- "examples/custom_object_type.cpp" @@ -306,7 +307,7 @@ using array_t = ArrayType>; --8<-- "examples/custom_array_type.hpp" ``` -??? example "Compiling and using it" +??? example "Example: use the custom `ArrayType`" ```cpp --8<-- "examples/custom_array_type.cpp" @@ -348,16 +349,16 @@ using array_t = ArrayType>; ### Always required - A member type `value_type` that is one byte wide and `char`-compatible. The library stores and processes UTF-8 - encoded `char` data and hands `data()` to `#!cpp std::strtoull`/`#!cpp std::strtoll`. + encoded `char` data and passes `data()` to functions that take a `#!cpp const char*`, such as `#!cpp std::strtod`. `#!cpp std::wstring`, `#!cpp std::u16string`, and `#!cpp std::u32string` are **not** valid choices; see the FAQ on [wide string handling](../../home/faq.md#wide-string-handling). - Constructors: default, copy, move, from `#!cpp const char*` (which must not be `#!cpp explicit`), from `#!cpp (const char*, size_type)`, and from `#!cpp (size_type, char)`; and copy or move assignment. - Member functions `size()`, `clear()`, `resize(n, c)`, `data()`, `push_back(char)`, and `operator[]` (const and non-const, returning references). `c_str()` and `back()` are **not** required. -- `data()` must return a pointer to a contiguous, **null-terminated** buffer -- the parser hands it to - `#!cpp std::strtoull`. A type whose `data()` is not null-terminated does not fail to compile; it silently - misparses numbers. +- `data()` must return a pointer to a contiguous, **null-terminated** buffer -- the parser may hand it to + `#!cpp std::strtod`, which reads up to the null character. A type whose `data()` is not null-terminated does not + fail to compile; it can silently misparse floating-point numbers. - `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md), and `append(const StringType&)`, used by the CBOR reader for indefinite-length strings. The library's internal string concatenation additionally has to append a `#!cpp char` and a `#!cpp const char*`; for each it selects between `append(arg)`, `#!cpp operator+=`, @@ -394,6 +395,7 @@ using array_t = ArrayType>; | [`to_bson`](../../api/basic_json/to_bson.md) | `find(value_type)` and `npos` | | [`parse`](../../api/basic_json/parse.md) from a `string_t` | the input adapters must accept it; otherwise pass a character range | | `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` | +| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) | | exception messages | `data()` and `size()`, or `begin()` and `end()` | ### Compatible types @@ -448,7 +450,7 @@ using array_t = ArrayType>; --8<-- "examples/custom_string_type.hpp" ``` -??? example "Compiling and using it" +??? example "Example: use the custom `StringType`" ```cpp --8<-- "examples/custom_string_type.cpp" @@ -535,8 +537,9 @@ therefore silently changes parse results rather than raising an error. See `NumberFloatType` must be one of `#!cpp float`, `#!cpp double`, or `#!cpp long double`: -- The [parser](../parsing/index.md) converts number literals with `#!cpp std::strtof`, `#!cpp std::strtod`, or - `#!cpp std::strtold`; the library provides overloads for exactly these three types. +- The [parser](../parsing/index.md) converts number literals with `#!cpp std::from_chars` or, as a fallback, with + `#!cpp std::strtof`, `#!cpp std::strtod`, or `#!cpp std::strtold`; the library provides overloads for exactly these + three types. - [`dump`](../../api/basic_json/dump.md) falls back to `#!cpp std::snprintf` with the `%g` and `%Lg` conversion specifiers, for which the library likewise provides only `#!cpp double` and `#!cpp long double` overloads (`#!cpp float` is promoted to `#!cpp double`). @@ -669,7 +672,7 @@ such a container to a `basic_json` value. --8<-- "examples/custom_binary_type.hpp" ``` -??? example "Compiling and using it" +??? example "Example: use the custom `BinaryType`" ```cpp --8<-- "examples/custom_binary_type.cpp" diff --git a/docs/mkdocs/docs/home/customers.md b/docs/mkdocs/docs/home/customers.md index 72802ff17..336cc55c8 100644 --- a/docs/mkdocs/docs/home/customers.md +++ b/docs/mkdocs/docs/home/customers.md @@ -3,7 +3,7 @@ The library is used in multiple projects, applications, operating systems, etc. The list below is not exhaustive, but the result of an internet search. If you know further customers of the library, [please let me know](mailto:mail@nlohmann.me). -[![](../images/customers.png)](../images/customers.png) +[![logos of customers using the library](../images/customers.png)](../images/customers.png) ## Space Exploration @@ -125,7 +125,7 @@ the result of an internet search. If you know further customers of the library, - [**GitHub CodeQL**](https://github.com/github/codeql/blob/main/shared/cpp/Diagnostics.h), a code analysis tool used for identifying security vulnerabilities and bugs in software through semantic queries - [**GoPro ngfx**](https://github.com/gopro/ngfx), a low-level graphics abstraction and profiling framework developed by GoPro - [**gRPC**](https://github.com/grpc/grpc/blob/master/tools/artifact_gen/utils.h), a high-performance universal remote procedure call framework -- [**Hex-Rays**](https://docs.hex-rays.com/user-guide/user-interface/licenses), a reverse engineering toolset for analyzing and decompiling binaries, primarily used for security research and vulnerability analysis +- [**Hex-Rays**](https://docs.hex-rays.com/core/user-interface/concepts/licenses), a reverse engineering toolset for analyzing and decompiling binaries, primarily used for security research and vulnerability analysis - [**ImHex**](https://github.com/WerWolv/ImHex), a hex editor designed for reverse engineering, providing advanced features for data analysis and manipulation - [**Intel GITS**](https://github.com/intel/gits), a tool for capturing and replaying graphics API calls for debugging and performance analysis - [**Intel GPA Framework**](https://intel.github.io/gpasdk-doc/src/licenses.html), a suite of cross-platform tools for capturing, analyzing, and optimizing graphics applications across different APIs @@ -240,9 +240,9 @@ the result of an internet search. If you know further customers of the library, - [**Manticore Search**](https://github.com/manticoresoftware/manticoresearch/blob/main/src/searchdhttpcompat.cpp), a database for search, offering full-text and vector queries - [**Milvus**](https://github.com/milvus-io/milvus/blob/master/internal/core/src/query/PlanImpl.h), a cloud-native vector database built for embedding similarity search - [**MongoDB**](https://github.com/mongodb/mongo/blob/master/src/mongo/replay/config_handler.cpp), a general-purpose document database -- [**MySQL Connector/C++**](https://docs.oracle.com/cd/E17952_01/connector-cpp-9.1-license-com-en/license-opentelemetry-cpp-com.html), a C++ library for connecting and interacting with MySQL databases -- [**MySQL NDB Cluster**](https://downloads.mysql.com/docs/licenses/cluster-9.0-com-en.pdf), a distributed database system that provides high availability and scalability for MySQL databases -- [**MySQL Shell**](https://downloads.mysql.com/docs/licenses/mysql-shell-8.0-gpl-en.pdf), an advanced client and code editor for interacting with MySQL servers, supporting SQL, Python, and JavaScript +- [**MySQL Connector/C++**](https://downloads.mysql.com/docs/licenses/connector-cpp-26.7-com-en.pdf), a C++ library for connecting and interacting with MySQL databases +- [**MySQL NDB Cluster**](https://downloads.mysql.com/docs/licenses/cluster-26.7-com-en.pdf), a distributed database system that provides high availability and scalability for MySQL databases +- [**MySQL Shell**](https://downloads.mysql.com/docs/licenses/mysql-shell-26.7-gpl-en.pdf), an advanced client and code editor for interacting with MySQL servers, supporting SQL, Python, and JavaScript - [**PrestoDB**](https://github.com/prestodb/presto/blob/master/presto-native-execution/presto_cpp/main/Announcer.cpp), a distributed SQL query engine designed for large-scale data analytics, originally developed by Facebook - [**ROOT Data Analysis Framework**](https://root.cern/doc/v614/classnlohmann_1_1basic__json.html), an open-source data analysis framework widely used in high-energy physics and other fields for data processing and visualization - [**Typesense**](https://github.com/typesense/typesense/blob/v31/include/join.h), an open source typo-tolerant search engine @@ -277,11 +277,11 @@ the result of an internet search. If you know further customers of the library, - [**Acronis Cyber Protect Cloud**](https://care.acronis.com/s/article/59533-Third-party-software-used-in-Acronis-Cyber-Protect-Cloud?language=en_US), an all-in-one data protection solution that combines backup, disaster recovery, and cybersecurity to safeguard business data from threats like ransomware - [**Baereos**](https://gitlab.tiger-computing.co.uk/packages/bareos/-/blob/tiger/bullseye/third-party/CLI11/examples/json.cpp), a backup solution that provides data protection and recovery options for various environments, including physical and virtual systems -- [**Bitdefender Home Scanner**](https://www.bitdefender.de/site/Main/view/home-scanner-open-source.html), a tool from Bitdefender that scans devices for malware and security threats, providing a safeguard against potential online dangers +- [**Bitdefender Home Scanner**](https://www.bitdefender.com/site/Main/view/home-scanner-open-source.html), a tool from Bitdefender that scans devices for malware and security threats, providing a safeguard against potential online dangers - [**Cisco MLS++**](https://github.com/cisco/mlspp), an implementation of the Messaging Layer Security protocol for end-to-end encrypted group messaging - [**Citrix Provisioning**](https://docs.citrix.com/en-us/provisioning/2203-ltsr/downloads/pvs-third-party-notices-2203.pdf), a solution that streamlines the delivery of virtual desktops and applications by allowing administrators to manage and provision resources efficiently across multiple environments - [**Citrix Virtual Apps and Desktops**](https://docs.citrix.com/en-us/citrix-virtual-apps-desktops/2305/downloads/third-party-notices-apps-and-desktops.pdf), a solution from Citrix that delivers virtual apps and desktops -- [**Cyberarc**](https://docs.cyberark.com/Downloads/Legal/Privileged%20Session%20Manager%20for%20SSH%20Third-Party%20Notices.pdf), a security solution that specializes in privileged access management, enabling organizations to control and monitor access to critical systems and data, thereby enhancing overall cybersecurity posture +- [**CyberArk**](https://docs.cyberark.com/Downloads/Legal/Privileged%20Session%20Manager%20for%20SSH%20Third-Party%20Notices.pdf), a security solution that specializes in privileged access management, enabling organizations to control and monitor access to critical systems and data, thereby enhancing overall cybersecurity posture - [**Deutsche Telekom sysrepo-plugins**](https://github.com/telekom/sysrepo-plugins), a collection of YANG datastore plugins used to manage network devices - [**Egnyte Desktop**](https://helpdesk.egnyte.com/hc/en-us/articles/360007071732-Third-Party-Software-Acknowledgements), a secure cloud storage solution designed for businesses, enabling file sharing, collaboration, and data management across teams while ensuring compliance and data protection - [**Elster**](https://www.secunet.com/en/about-us/press/article/elstersecure-bietet-komfortablen-login-ohne-passwort-dank-secunet-protect4use), a digital platform developed by German tax authorities for secure and efficient electronic tax filing and management using secunet protect4use diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 407f3c3f1..c78aeaa68 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -41,7 +41,7 @@ Exceptions are used widely within the library. They can, however, be switched of Note that [`JSON_THROW_USER`](../api/macros/json_throw_user.md) should leave the current scope (e.g., by throwing or aborting), as continuing after it may yield undefined behavior. -??? example +??? example "Example: switch off exceptions and log errors before aborting" The code below switches off exceptions and creates a log entry with a detailed error message in case of errors. @@ -67,7 +67,7 @@ See [documentation of `JSON_TRY_USER`, `JSON_CATCH_USER` and `JSON_THROW_USER`]( Exceptions in the library are thrown in the local context of the JSON value they are detected. This makes detailed diagnostics messages, and hence debugging, difficult. -??? example +??? example "Example: standard diagnostic message" ```cpp --8<-- "examples/diagnostics_standard.cpp" @@ -85,7 +85,7 @@ To create better diagnostics messages, each JSON value needs a pointer to its pa As this global context comes at the price of storing one additional pointer per JSON value and runtime overhead to maintain the parent relation, extended diagnostics are disabled by default. They can, however, be enabled by defining the preprocessor symbol [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) to `1` before including `json.hpp`. -??? example +??? example "Example: extended diagnostic message with `JSON_DIAGNOSTICS`" ```cpp --8<-- "examples/diagnostics_extended.cpp" @@ -118,7 +118,7 @@ Exceptions have ids 1xx. is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). -??? example +??? example "Example: catch a `parse_error` exception" The following code shows how a `parse_error` exception can be caught. @@ -395,7 +395,7 @@ the expected semantics. Exceptions have ids 2xx. -??? example +??? example "Example: catch an `invalid_iterator` exception" The following code shows how an `invalid_iterator` exception can be caught. @@ -421,7 +421,7 @@ The iterators passed to constructor `basic_json(InputIT first, InputIT last)` ar ### json.exception.invalid_iterator.202 -In the [erase](../api/basic_json/erase.md) or insert function, the passed iterator `pos` does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +In the [erase](../api/basic_json/erase.md) or [insert](../api/basic_json/insert.md) function, the passed iterator `pos` does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. !!! failure "Example messages" @@ -454,7 +454,7 @@ When an iterator range for a primitive type (number, boolean, or string) is pass ### json.exception.invalid_iterator.205 -When an iterator for a primitive type (number, boolean, or string) is passed to an [erase](../api/basic_json/erase.md) function, the iterator has to be the `begin()` iterator, because it is the only way to address the stored value. All other iterators are invalid. +When an iterator for a primitive type (number, boolean, or string) is passed to an [erase](../api/basic_json/erase.md) function, the iterator has to be the [`begin()`](../api/basic_json/begin.md) iterator, because it is the only way to address the stored value. All other iterators are invalid. !!! failure "Example message" @@ -545,7 +545,7 @@ The order of object iterators cannot be compared, because JSON objects are unord ### json.exception.invalid_iterator.214 -Cannot retrieve value from iterator: The iterator either refers to a null value, or it refers to a primitive type (number, boolean, or string), but does not match the iterator returned by `begin()`. +Cannot retrieve value from iterator: The iterator either refers to a null value, or it refers to a primitive type (number, boolean, or string), but does not match the iterator returned by [`begin()`](../api/basic_json/begin.md). !!! failure "Example message" @@ -559,7 +559,7 @@ This exception is thrown in case of a type error; that is, a library function is Exceptions have ids 3xx. -??? example +??? example "Example: catch a `type_error` exception" The following code shows how a `type_error` exception can be caught. @@ -611,7 +611,7 @@ To retrieve a reference to a value stored in a `basic_json` object with `get_ref ### json.exception.type_error.304 -The `at()` member functions can only be executed for certain JSON types. +The [`at()`](../api/basic_json/at.md) member functions can only be executed for certain JSON types. !!! failure "Example messages" @@ -624,7 +624,7 @@ The `at()` member functions can only be executed for certain JSON types. ### json.exception.type_error.305 -The `operator[]` member functions can only be executed for certain JSON types. +The [`operator[]`](../api/basic_json/operator%5B%5D.md) member functions can only be executed for certain JSON types. !!! failure "Example messages" @@ -637,7 +637,7 @@ The `operator[]` member functions can only be executed for certain JSON types. ### json.exception.type_error.306 -The `value()` member functions can only be executed for certain JSON types. +The [`value()`](../api/basic_json/value.md) member functions can only be executed for certain JSON types. !!! failure "Example message" @@ -657,7 +657,7 @@ The [`erase()`](../api/basic_json/erase.md) member functions can only be execute ### json.exception.type_error.308 -The `push_back()` and `operator+=` member functions can only be executed for certain JSON types. +The [`push_back()`](../api/basic_json/push_back.md) and [`operator+=`](../api/basic_json/operator+=.md) member functions can only be executed for certain JSON types. !!! failure "Example message" @@ -667,7 +667,7 @@ The `push_back()` and `operator+=` member functions can only be executed for cer ### json.exception.type_error.309 -The `insert()` member functions can only be executed for certain JSON types. +The [`insert()`](../api/basic_json/insert.md) member functions can only be executed for certain JSON types. !!! failure "Example messages" @@ -680,7 +680,7 @@ The `insert()` member functions can only be executed for certain JSON types. ### json.exception.type_error.310 -The `swap()` member functions can only be executed for certain JSON types. +The [`swap()`](../api/basic_json/swap.md) member functions can only be executed for certain JSON types. !!! failure "Example message" @@ -690,7 +690,7 @@ The `swap()` member functions can only be executed for certain JSON types. ### json.exception.type_error.311 -The `emplace()` and `emplace_back()` member functions can only be executed for certain JSON types. +The [`emplace()`](../api/basic_json/emplace.md) and [`emplace_back()`](../api/basic_json/emplace_back.md) member functions can only be executed for certain JSON types. !!! failure "Example messages" @@ -703,7 +703,7 @@ The `emplace()` and `emplace_back()` member functions can only be executed for c ### json.exception.type_error.312 -The `update()` member functions can only be executed for certain JSON types. +The [`update()`](../api/basic_json/update.md) member functions can only be executed for certain JSON types. !!! failure "Example message" @@ -713,7 +713,7 @@ The `update()` member functions can only be executed for certain JSON types. ### json.exception.type_error.313 -The `unflatten` function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well-defined. +The [`unflatten()`](../api/basic_json/unflatten.md) function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well-defined. !!! failure "Example message" @@ -723,7 +723,7 @@ The `unflatten` function converts an object whose keys are JSON Pointers back in ### json.exception.type_error.314 -The `unflatten` function only works for an object whose keys are JSON Pointers. +The [`unflatten()`](../api/basic_json/unflatten.md) function only works for an object whose keys are JSON Pointers. !!! failure "Example message" @@ -735,7 +735,7 @@ The `unflatten` function only works for an object whose keys are JSON Pointers. ### json.exception.type_error.315 -The `unflatten()` function only works for an object whose keys are JSON Pointers and whose values are primitive. +The [`unflatten()`](../api/basic_json/unflatten.md) function only works for an object whose keys are JSON Pointers and whose values are primitive. !!! failure "Example message" @@ -747,7 +747,7 @@ The `unflatten()` function only works for an object whose keys are JSON Pointers ### json.exception.type_error.316 -The `dump()` function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. +The [`dump()`](../api/basic_json/dump.md) function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. See the FAQ entry on [serializing untrusted or invalid UTF-8](faq.md#serializing-untrusted-or-invalid-utf-8) for background and the recommended fix. !!! failure "Example message" @@ -788,7 +788,7 @@ This exception is thrown in case a library function is called on an input parame Exceptions have ids 4xx. -??? example +??? example "Example: catch an `out_of_range` exception" The following code shows how an `out_of_range` exception can be caught. @@ -1009,7 +1009,7 @@ other exception types. Exceptions have ids 5xx. -??? example +??? example "Example: catch an `other_error` exception" The following code shows how an `other_error` exception can be caught. diff --git a/docs/mkdocs/docs/home/faq.md b/docs/mkdocs/docs/home/faq.md index 8b3602bd1..ca113004c 100644 --- a/docs/mkdocs/docs/home/faq.md +++ b/docs/mkdocs/docs/home/faq.md @@ -44,9 +44,9 @@ for objects. json j = json::array({true}); // [true] ``` -**Opt-in copy semantics (since version 3.12.0)** +**Opt-in copy semantics (since version 3.13.0)** -If you define `JSON_BRACE_INIT_COPY_SEMANTICS` to `1` before including the library, single-element brace initialization is treated as copy/move instead of creating a single-element array: +If you define [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) to `1` before including the library, single-element brace initialization is treated as copy/move instead of creating a single-element array: ```cpp #define JSON_BRACE_INIT_COPY_SEMANTICS 1 @@ -85,7 +85,7 @@ The library supports **Unicode input** as follows: - The library will not replace [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1). - Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors. - The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs. -- When you store strings with different encodings in the library, calling [`dump()`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a50ec80b02d0f3f51130d4abb5d1cfdc5.html#a50ec80b02d0f3f51130d4abb5d1cfdc5) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers. +- When you store strings with different encodings in the library, calling [`dump()`](../api/basic_json/dump.md) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers. In most cases, the parser is right to complain, because the input is not UTF-8 encoded. This is especially true for Microsoft Windows, where Latin-1 or ISO 8859-1 is often the standard encoding. @@ -94,7 +94,7 @@ In most cases, the parser is right to complain, because the input is not UTF-8 e !!! question "Questions" - - Why does `json::parse()` silently ignore part of my input? + - Why does [`json::parse()`](../api/basic_json/parse.md) silently ignore part of my input? - Why does a `std::string`/buffer with extra data after the JSON text parse without error, while a similar-looking string with extra text does not? A `'\0'` (NUL) byte anywhere in the input is treated the same as the real end of the input, rather than as an ordinary (and, outside of a string, invalid) byte. Everything from that byte onward is silently ignored, without a parse error — including further, otherwise well-formed JSON: @@ -197,8 +197,8 @@ same object -- is a data race and requires external synchronization (e.g., a `st Does this library support JSON Schema validation? Not directly, but the companion project [json-schema-validator](https://github.com/pboettch/json-schema-validator) -builds JSON Schema (draft 4, 6, 7, and 2019-09) validation on top of this library and is a common recommendation -for this use case. +builds JSON Schema (draft 7; draft 4 in its older, now-superseded 1.x releases) validation on top of this library +and is a common recommendation for this use case. ## Exceptions @@ -296,15 +296,13 @@ If you get ambiguous-overload errors when passing a JSON value to `fmt::format`/ Why does the code not compile with Android SDK? -Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default. +Since [NDK r18](https://github.com/android/ndk/wiki/Changelog-r18) (2018), GCC and the `gnustl`/`stlport` C++ +libraries have been removed from the Android NDK; Clang and `libc++` are now the only compiler and C++ library, and +they support C++11 and later out of the box. With a current NDK, no special configuration is needed to use this +library. -```ini -APP_STL := c++_shared -NDK_TOOLCHAIN_VERSION := clang3.6 -APP_CPPFLAGS += -frtti -fexceptions -``` - -The code compiles successfully with [Android NDK](https://developer.android.com/ndk/index.html?hl=ml), Revision 9 - 11 (and possibly later) and [CrystaX's Android NDK](https://www.crystax.net/en/android/ndk) version 10. +Only very old NDKs (before r18), which defaulted to GCC and `gnustl`, lacked C++11 library features such as +`std::to_string`. If you run into this, update to a current NDK. ### Missing STL function @@ -314,4 +312,6 @@ The code compiles successfully with [Android NDK](https://developer.android.com/ - Why do I get a compilation error `'to_string' is not a member of 'std'` (or similarly, for `strtod` or `strtof`)? - Why does the code not compile with MinGW or Android SDK? -This is not an issue with the code, but rather with the compiler itself. On Android, see above to build with a newer environment. For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. For Android NDK using `APP_STL := gnustl_static`, please refer to [this discussion](https://github.com/nlohmann/json/issues/219). +This is not an issue with the code, but rather with the compiler itself. On Android, use a current NDK (see above). +For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and +[this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. diff --git a/docs/mkdocs/docs/home/license.md b/docs/mkdocs/docs/home/license.md index 863b7c1d6..11d0ec529 100644 --- a/docs/mkdocs/docs/home/license.md +++ b/docs/mkdocs/docs/home/license.md @@ -1,6 +1,6 @@ # License - +OSI approved license The class is licensed under the [MIT License](https://opensource.org/licenses/MIT): diff --git a/docs/mkdocs/docs/home/releases.md b/docs/mkdocs/docs/home/releases.md index cdaca4c0d..f91f82dda 100644 --- a/docs/mkdocs/docs/home/releases.md +++ b/docs/mkdocs/docs/home/releases.md @@ -4,6 +4,11 @@ This page summarizes the notable changes of every release and links to the relev The **complete release notes** — including all changes, the download files, and their checksums — are published on the [GitHub releases page](https://github.com/nlohmann/json/releases). +!!! info "Unreleased changes" + + This documentation is built from the `develop` branch and may describe changes that are not part of a release + yet. Their version numbers are followed by an unreleased badge. + ## v3.12.0 (2025-04-11) Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible. diff --git a/docs/mkdocs/docs/home/sponsors.md b/docs/mkdocs/docs/home/sponsors.md index 6c3a558fb..ddbfbf3f0 100644 --- a/docs/mkdocs/docs/home/sponsors.md +++ b/docs/mkdocs/docs/home/sponsors.md @@ -9,7 +9,7 @@ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nl ## Named Sponsors -- [Michael Hartmann](https://github.com/reFX-Mike) +- Michael Hartmann - [Stefan Hagen](https://github.com/sthagen) - [Steve Sperandeo](https://github.com/homer6) - [Robert Jefe LindstƤdt](https://github.com/eljefedelrodeodeljefe) diff --git a/docs/mkdocs/docs/index.md b/docs/mkdocs/docs/index.md index 0e49c836c..091eca5e8 100644 --- a/docs/mkdocs/docs/index.md +++ b/docs/mkdocs/docs/index.md @@ -1,3 +1,114 @@ # JSON for Modern C++ -![](images/json.gif) +![JSON for Modern C++](images/json.gif) + +JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-class C++ data type, using the operator +magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like +Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no +complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times +down. See [header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code +coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google +OSS-Fuzz. + +## Quick start + +Add the single header to your project and use the library like this: + +```cpp +#include +#include + +using json = nlohmann::json; + +int main() +{ + // parse a JSON string + json j = json::parse(R"({"happy": true, "pi": 3.141})"); + + // access and modify values + j["name"] = "Niels"; + j["list"] = {1, 0, 2}; + + // serialize with an indent of 4 spaces + std::cout << j.dump(4) << '\n'; +} +``` + +Get the library by copying the single header [`json.hpp`](https://github.com/nlohmann/json/releases) from the +releases page into a directory `nlohmann` on your include path, or by installing it with a package manager: + +```sh +brew install nlohmann-json # Homebrew +vcpkg install nlohmann-json # vcpkg +``` + +```cmake +find_package(nlohmann_json 3.12.0 REQUIRED) +target_link_libraries(myproject PRIVATE nlohmann_json::nlohmann_json) +``` + +See [Integration](integration/index.md) for CMake in detail, all supported package managers (Conan, Meson, Bazel, +Conda, and more), and pkg-config. + +## Explore the documentation + +
+ +- :octicons-rocket-24:{ .lg .middle } __Features__ + + --- + + Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, and more. + + [:octicons-arrow-right-24: Features](features/index.md) + +- :octicons-package-24:{ .lg .middle } __Integration__ + + --- + + Add the library to your project via a single header, CMake, a package manager, or pkg-config. + + [:octicons-arrow-right-24: Integration](integration/index.md) + +- :octicons-book-24:{ .lg .middle } __API documentation__ + + --- + + The complete reference for `basic_json` and its member functions, types, and related classes. + + [:octicons-arrow-right-24: API documentation](api/basic_json/index.md) + +- :octicons-question-24:{ .lg .middle } __FAQ__ + + --- + + Answers to common questions and known surprises when using the library. + + [:octicons-arrow-right-24: FAQ](home/faq.md) + +- :octicons-tag-24:{ .lg .middle } __Releases__ + + --- + + What changed in each release, with links to the relevant documentation. + + [:octicons-arrow-right-24: Releases](home/releases.md) + +- :octicons-people-24:{ .lg .middle } __Community__ + + --- + + The ecosystem, contribution guidelines, governance, and quality assurance around the project. + + [:octicons-arrow-right-24: Community](community/index.md) + +
+ +!!! info "Unreleased changes" + + This documentation is built from the `develop` branch and may describe changes that are not part of a release + yet. Their version numbers are followed by an unreleased badge; see + [Releases](home/releases.md) for what shipped in each version. + +The library is licensed under the [MIT License](home/license.md). The source code, issue tracker, and discussions +are on [GitHub](https://github.com/nlohmann/json). diff --git a/docs/mkdocs/docs/integration/bazel/MODULE.bazel b/docs/mkdocs/docs/integration/bazel/MODULE.bazel index ba902be27..5d43f9b90 100644 --- a/docs/mkdocs/docs/integration/bazel/MODULE.bazel +++ b/docs/mkdocs/docs/integration/bazel/MODULE.bazel @@ -1 +1 @@ -bazel_dep(name = "nlohmann_json", version = "3.11.3.bcr.1") +bazel_dep(name = "nlohmann_json", version = "3.12.0.bcr.2") diff --git a/docs/mkdocs/docs/integration/cmake.md b/docs/mkdocs/docs/integration/cmake.md index 4160584d5..785fc3ed6 100644 --- a/docs/mkdocs/docs/integration/cmake.md +++ b/docs/mkdocs/docs/integration/cmake.md @@ -5,7 +5,8 @@ You can use the `nlohmann_json::nlohmann_json` interface target in CMake. This target populates the appropriate usage requirements for [`INTERFACE_INCLUDE_DIRECTORIES`](https://cmake.org/cmake/help/latest/prop_tgt/INTERFACE_INCLUDE_DIRECTORIES.html) to point to the appropriate include directories and [`INTERFACE_COMPILE_FEATURES`](https://cmake.org/cmake/help/latest/prop_tgt/INTERFACE_COMPILE_FEATURES.html) -for the necessary C++11 flags. +for the necessary C++11 flags. Most [package managers](package_managers.md) that provide a CMake package configuration +for this library expose this same target. ### External @@ -138,7 +139,7 @@ Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic- !!! warning "Does not apply to a pre-installed package" This option only takes effect when building nlohmann/json from source as part of your own - CMake project (e.g. via [`FetchContent`](#fetchcontent) or [`add_subdirectory`](#external)). + CMake project (e.g. via [`FetchContent`](#fetchcontent) or [`add_subdirectory`](#embedded)). It has **no effect** on a package that was already built and installed elsewhere (Homebrew, vcpkg, a system package, etc.) — the resulting compile definition is baked into the exported `nlohmann_jsonTargets.cmake` at install time, and `set(JSON_Diagnostics ON)` before @@ -182,15 +183,22 @@ Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JS ### `JSON_GlobalUDLs` Place user-defined string literals in the global namespace by defining the macro -[`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md). This option is `OFF` by default. +[`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md). This option is `ON` by default; see the +[migration guide](migration_guide.md#import-namespace-literals-for-udls) for how to prepare code for the next major +release, where the literals are removed from the global namespace. ### `JSON_ImplicitConversions` -Enable implicit conversions by defining macro [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md). This option is `ON` by default. +Enable implicit conversions by defining macro +[`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md). This option is `ON` by default; see +the [migration guide](migration_guide.md#replace-implicit-conversions) for how to prepare code for the next major +release, where implicit conversions are switched off by default. ### `JSON_Install` -Install CMake targets during install step. This option is `ON` by default if the library's CMake project is the top project. +Install CMake targets during install step. This option is `ON` by default if the library's CMake project is the top +project. Installing also generates a [pkg-config](pkg-config.md) file for tools that rely on `pkg-config` instead of +CMake. ### `JSON_LegacyDiscardedValueComparison` @@ -209,6 +217,13 @@ Treat the library headers like system headers (i.e., adding `SYSTEM` to the [`ta Reject a `'\0'` (NUL) byte in the input instead of treating it as end of input, by defining the macro [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md). This option is `OFF` by default. +### `JSON_TestSimdutf` + +Build the unit tests against the [simdutf](https://github.com/simdutf/simdutf) UTF-8 validation backend by defining +[`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) for every test target. simdutf is fetched during configuration; +its version is set by the cache variable `JSON_SIMDUTF_VERSION`. This option is `OFF` by default. Depends on +`JSON_BuildTests`. + ### `JSON_Valgrind` Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`. diff --git a/docs/mkdocs/docs/integration/index.md b/docs/mkdocs/docs/integration/index.md index 99bf088b6..5c3995a95 100644 --- a/docs/mkdocs/docs/integration/index.md +++ b/docs/mkdocs/docs/integration/index.md @@ -1,4 +1,34 @@ -# Header only +# Integration + +There are several ways to add this header-only library to a C++ project. The following flowchart summarizes how to +pick one: + +```mermaid +flowchart TD + A[Add the library to a C++ project] --> B{Already using CMake?} + B -- no --> C{Using pkg-config or plain Makefiles?} + C -- yes --> D[pkg-config] + C -- no --> E[Copy the single header] + B -- yes --> F{Library installed system-wide?} + F -- yes --> G["find_package()"] + F -- no --> H{Use a package manager?} + H -- yes --> I[Package manager] + H -- no --> J["add_subdirectory() or FetchContent"] +``` + +- **Copy the single header**, as described [below](#header-only) — no build-system integration required. +- **CMake**: use [`find_package()`](cmake.md#external) if the library is already installed, + [`add_subdirectory()`](cmake.md#embedded) to embed the source tree, or [`FetchContent`](cmake.md#fetchcontent) to + download it at configure time; see [CMake](cmake.md). +- **Package managers**: install the library with a package manager such as Homebrew, Conan, or vcpkg; see + [Package Managers](package_managers.md). +- **pkg-config**: if you use bare Makefiles instead of CMake, [pkg-config](pkg-config.md) can supply the include flags + for an already-installed library. + +Once the library is integrated, see the [Migration Guide](migration_guide.md) for how to keep your code future-proof +across releases. + +## Header only [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp) is the single required file in `single_include/nlohmann` or [released here](https://github.com/nlohmann/json/releases). You need to add diff --git a/docs/mkdocs/docs/integration/migration_guide.md b/docs/mkdocs/docs/integration/migration_guide.md index 8b718d969..29d54b6df 100644 --- a/docs/mkdocs/docs/integration/migration_guide.md +++ b/docs/mkdocs/docs/integration/migration_guide.md @@ -1,6 +1,8 @@ # Migration Guide -This page collects some guidelines on how to future-proof your code for future versions of this library. +This page collects some guidelines on how to future-proof your code for future versions of this library. For how to +add the library to your project in the first place, see [Integration](index.md), [CMake](cmake.md), or +[Package Managers](package_managers.md). ## Replace deprecated functions @@ -9,7 +11,7 @@ deprecations are annotated with [`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which function to use instead. -#### Parsing +### Parsing - Function `friend std::istream& operator<<(basic_json&, std::istream&)` is deprecated since 3.0.0. Please use [`friend std::istream& operator>>(std::istream&, basic_json&)`](../api/operator_gtgt.md) instead. @@ -33,9 +35,11 @@ function to use instead. - Passing iterator pairs or pointer/length pairs to parsing functions ([`parse`](../api/basic_json/parse.md), [`accept`](../api/basic_json/accept.md), [`sax_parse`](../api/basic_json/sax_parse.md), [`from_cbor`](../api/basic_json/from_cbor.md), [`from_msgpack`](../api/basic_json/from_msgpack.md), - [`from_ubjson`](../api/basic_json/from_ubjson.md), and [`from_bson`](../api/basic_json/from_bson.md) via initializer + [`from_ubjson`](../api/basic_json/from_ubjson.md), and [`from_bson`](../api/basic_json/from_bson.md)) via initializer lists is deprecated since 3.8.0. Instead, pass two iterators; for instance, call `from_cbor(ptr, ptr+len)` instead of - `from_cbor({ptr, len})`. + `from_cbor({ptr, len})`. Likewise, passing a pointer and a length as two separate arguments to `from_cbor`, + `from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0; call `from_cbor(ptr, ptr+len)` instead of + `from_cbor(ptr, len)`. === "Deprecated" @@ -51,7 +55,7 @@ function to use instead. bool ok = nlohmann::json::accept(s, s + std::strlen(s)); ``` -#### JSON Pointers +### JSON Pointers - Comparing JSON Pointers with strings via [`operator==`](../api/json_pointer/operator_eq.md) and [`operator!=`](../api/json_pointer/operator_ne.md) is deprecated since 3.11.2. To compare a @@ -93,7 +97,9 @@ function to use instead. - Passing a `basic_json` specialization as template parameter `RefStringType` to [`json_pointer`](../api/json_pointer/index.md) is deprecated since 3.11.0. The string type can now be directly - provided. + provided. This also applies to passing such a JSON pointer to [`at`](../api/basic_json/at.md), + [`contains`](../api/basic_json/contains.md), [`operator[]`](../api/basic_json/operator%5B%5D.md), and + [`value`](../api/basic_json/value.md). === "Deprecated" @@ -108,10 +114,11 @@ function to use instead. nlohmann::json_pointer ptr("/foo/bar/1"); ``` - Thereby, `nlohmann::my_json::json_pointer` is an alias for `nlohmann::json_pointer` and is always an - alias to the `json_pointer` with the appropriate string type for all specializations of `basic_json`. + Thereby, `my_json::json_pointer` is an alias for `nlohmann::json_pointer`; in general, + `basic_json::json_pointer` is always an alias to the `json_pointer` with the appropriate string type for all + specializations of `basic_json`. -#### Miscellaneous functions +### Miscellaneous functions - The function `iterator_wrapper` is deprecated since 3.1.0. Please use the member function [`items`](../api/basic_json/items.md) instead. @@ -260,7 +267,7 @@ exact version and configuration is relevant, use macro } ``` -## Do not use the `details` namespace +## Do not use the `detail` namespace -The `details` namespace is not part of the public API of the library and can change in any version without an -announcement. Do not rely on any function or type in the `details` namespace. +The `nlohmann::detail` namespace is not part of the public API of the library and can change in any version without +an announcement. Do not rely on any function or type in the `detail` namespace. diff --git a/docs/mkdocs/docs/integration/msys2/example.cpp b/docs/mkdocs/docs/integration/msys2/example.cpp new file mode 100644 index 000000000..1a7ac4de2 --- /dev/null +++ b/docs/mkdocs/docs/integration/msys2/example.cpp @@ -0,0 +1,10 @@ +#include +#include +#include + +using json = nlohmann::json; + +int main() +{ + std::cout << std::setw(4) << json::meta() << std::endl; +} diff --git a/docs/mkdocs/docs/integration/nuget/nuget-package-content.png b/docs/mkdocs/docs/integration/nuget/nuget-package-content.png deleted file mode 100644 index cc975b98bc7bc8e77468e8ee888a972de772418a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19422 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∈`gy(ms)zid=_*Hti#KmRgg*aY0PdA%gZIsB6NZ9?Af1#6Zznh^ z?|-b2|NOOY%AB7(Ng5Yd-Huoq?V@Z#+oFkeXNj=Em2oNqF*Vi4Y+9X?SXoH&Uhk4S{mp{{sT4*#&0swLsU_)|#Dq^#{^8 z)R2-B5?X;7lBoQ4x|awC0=}&S=@}hCcqtN4p2<#8kQqC{rUPj~^E$^Vj3WwMv7ZF4 zy(*MKCR?k72rWxb1*Wns=6!+$j^ZCUz0$C7u6wsjwAVq^TzG>_OLP+KHWL~%H#Q;e|Kq2w9=#Rs_5pKY>Trr$L z`8SXI=QCRb*yW_(%75g-aqfWc(f>iK_L3n0?}ej*F2c}O{%5x?fw8M&f*+Pm zo+UJ|6Vzh_KmmiH%}Eh4Sm1SV9-50GLh($&wvz{;UyU~b7U(~{zex%quQ+WK_i1Rl zmD{o{0QzMXLI0L7JRh=^B>-+7YgtJwZA%tKGnxryeuEo4$Ijbie8)4!ee?$2b|`LNR&VZ93ER&leE{fCirX?0!bu<9KL4VrI_mo8q(}D zS(8RM3F4`OpOM9f&gYC44k7Po70JrX7SmIWRJQX(1T6o{&~7zme20wfdgp!ZyzSE_ zVX8XNL`T1~zw!yMqUY1pJVpPFeDm~xC{DzsD2com4JT{d1XYZVDVF_r=u3y|VT^5m zF_~3Y&i+Z0Na5p@YB zTH`}HSB537i>ZDkJq-mT;XK8+$O;#@yBIsOm>=%Qv;z8?iOd~{a-o0y>e#{+znoA= z2Y+SQV_=-p87|JY<1DiFvL=fn)o*|eSk8DLXaFp;;&ormzWWc2gFKiERh~h4W^w(6 zfcopkHpSKmOp#BP_I|H9RfNnxNR}#-F|IIEH*IQt;+klb*+g-Yh7@Zlp=*AP(}!GC zb=7|*rKJ8g|LXesI_8$q<Dsdm( z;I+@dKjU*7&r34Lz-BVfgl7ID^A$% z;2U|r>M}QI@AITkkjB000I-OM6$D7@{Cl<}T~Q#hBSYLZ5bV=j)y6po4#7 zq522OZZ%xqh6DG|dP!dq;cE2+jLlbJH@7`7wCROavdY&j|A*Ka3~aN{H=ZDO$iMRN z1T3Jj*1x9w5j}pJVP+C|74-T(^SvRVe^{IjrcHZYJyx57lE%-+#T^td`+^S+YzEo; z_@%Y_=r^=zVfRY9c0ZT$OTKW-yKKNWz!gxBLi{DZ?P5#4WVSh!z&)f6!}&FcwX->V z3jfgv2|UA!ymUM2jNx|w)M^p@ru28DLb%>a_)V)(H&feUe>JH3%+G4bz14u5+e4d(MKt&q(BhsI@wS?F~p z-7B-I;6-~t?<^v)--jaECn7QHa6lYW9|m6LKE4H04o^x>oLutx*bx9Y-Zf=6|g51HeU0BF@*=DT_vcw%Cj^R)c7-wEF@Nzx+T5 zo~<-0yak?rNnkXaiqS8BB@c0n5ymTd@w?|+%}7DGbIT$jN{Lb3bydHHtT)da;!64q zgm788u1B;08{x*c8P-31`K_?0jq*2Ek~m*r3N#hjpd`pGZ?mAswEJ7ZC9zy`=FnzPE=Zd)YmC>JBVFrA<|IZOYpNChv2M9s{5VjBm+Q43|X&f(J{NpE( aufd>S8@@=`0RL~-8xa9X{t{m8@Bar_#?$Zs diff --git a/docs/mkdocs/docs/integration/nuget/nuget-project-changes.png b/docs/mkdocs/docs/integration/nuget/nuget-project-changes.png deleted file mode 100644 index eb2a520a8f27bd5e84b03466cd1d79c329203286..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30826 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 Zc9O8;;I^_u@I8bfWF!>DD?|-H{Xb<1g}49! diff --git a/docs/mkdocs/docs/integration/nuget/nuget-project-makefile.png b/docs/mkdocs/docs/integration/nuget/nuget-project-makefile.png deleted file mode 100644 index 74657f264d65d6a2e8f6602665452122f1029a08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55236 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 diff --git a/docs/mkdocs/docs/integration/package_managers.md b/docs/mkdocs/docs/integration/package_managers.md index 28f95e584..d5357b8ff 100644 --- a/docs/mkdocs/docs/integration/package_managers.md +++ b/docs/mkdocs/docs/integration/package_managers.md @@ -31,13 +31,17 @@ When executed, this program should create output similar to --8<-- "examples/meta.output" ``` +Many of the package managers below install a CMake package configuration that exposes the same +`nlohmann_json::nlohmann_json` interface target described in [CMake](cmake.md); their CMake examples below link +against that target. + ## Homebrew !!! abstract "Summary" formula: [**`nlohmann-json`**](https://formulae.brew.sh/formula/nlohmann-json) - - [![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) + - [![Homebrew package](https://img.shields.io/homebrew/v/nlohmann-json)](https://formulae.brew.sh/formula/nlohmann-json) - :octicons-tag-24: Available versions: current version and development version (with `--HEAD` parameter) - :octicons-rocket-24: The formula is updated with every release. - :octicons-person-24: Maintainer: Niels Lohmann @@ -121,8 +125,8 @@ meson wrap install nlohmann_json Please see the Meson project for any issues regarding the packaging. The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in -which case a pkg-config file is installed. To use it, have your build system require the `nlohmann_json` -pkg-config dependency. In Meson, it is preferred to use the +which case a [pkg-config](pkg-config.md) file is installed. To use it, have your build system require the +`nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than using the subproject directly. @@ -165,7 +169,7 @@ using the subproject directly. This repository provides a [Bazel](https://bazel.build/) `MODULE.bazel` and a corresponding `BUILD.bazel` file. Therefore, this repository can be referenced within a `MODULE.bazel` by rules such as `archive_override`, `git_override`, or `local_path_override`. To use the library, you need to depend on the target `@nlohmann_json//:json` (i.e., via `deps` attribute). -??? example +??? example "Example: Bazel module with `bazel_dep`" 1. Create the following files: @@ -173,7 +177,7 @@ repository can be referenced within a `MODULE.bazel` by rules such as `archive_o --8<-- "integration/bazel/BUILD" ``` - ```ini title="WORKSPACE" + ```ini title="MODULE.bazel" --8<-- "integration/bazel/MODULE.bazel" ``` @@ -194,7 +198,7 @@ repository can be referenced within a `MODULE.bazel` by rules such as `archive_o recipe: [**`nlohmann_json`**](https://conan.io/center/recipes/nlohmann_json) - - [![ConanCenter package](https://repology.org/badge/version-for-repo/conancenter/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) + - [![ConanCenter package](https://img.shields.io/conan/v/nlohmann_json)](https://conan.io/center/recipes/nlohmann_json) - :octicons-tag-24: Available versions: current version and older versions (see [Conan Center](https://conan.io/center/recipes/nlohmann_json)) - :octicons-rocket-24: The package is updated automatically via @@ -205,7 +209,7 @@ repository can be referenced within a `MODULE.bazel` by rules such as `archive_o If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add `nlohmann_json/x.y.z` to your `conanfile`'s requires, where `x.y.z` is the release version you want to use. -??? example +??? example "Example: CMake with the Conan toolchain" 1. Create the following files: @@ -240,7 +244,7 @@ requires, where `x.y.z` is the release version you want to use. package: [**`nlohmann-json`**](https://packages.spack.io/package.html?name=nlohmann-json) - - [![Spack package](https://repology.org/badge/version-for-repo/spack/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) + - [![Spack package](https://img.shields.io/spack/v/nlohmann-json)](https://packages.spack.io/package.html?name=nlohmann-json) - :octicons-tag-24: Available versions: current version and older versions (see [Spack package](https://packages.spack.io/package.html?name=nlohmann-json)) - :octicons-rocket-24: The package is updated with every release. @@ -257,7 +261,7 @@ spack install nlohmann-json Please see the [Spack project](https://github.com/spack/spack) for any issues regarding the packaging. -??? example +??? example "Example: CMake with a Spack-installed package" 1. Create the following files: @@ -309,7 +313,7 @@ hunter_add_package(nlohmann_json) Please see the Hunter project for any issues regarding the packaging. -??? example +??? example "Example: CMake with HunterGate" 1. Create the following files: @@ -341,7 +345,7 @@ Please see the Hunter project for any issues regarding the packaging. package: [**`nlohmann-json`**](https://github.com/Microsoft/vcpkg/tree/master/ports/nlohmann-json) - - [![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) + - [![vcpkg package](https://img.shields.io/vcpkg/v/nlohmann-json)](https://vcpkg.io/en/package/nlohmann-json) - :octicons-tag-24: Available versions: current version - :octicons-rocket-24: The package is updated with every release. - :octicons-file-24: File issues at the [vcpkg issue tracker](https://github.com/microsoft/vcpkg/issues) @@ -356,7 +360,7 @@ vcpkg install nlohmann-json and follow the then displayed descriptions. Please see the vcpkg project for any issues regarding the packaging. -??? example +??? example "Example: CMake with the vcpkg toolchain" 1. Create the following files: @@ -401,16 +405,16 @@ cget install nlohmann/json A specific version can be installed with `cget install nlohmann/json@v3.12.0`. Also, the multiple header version can be installed by adding the `-DJSON_MultipleHeaders=ON` flag (i.e., `cget install nlohmann/json -DJSON_MultipleHeaders=ON`). -??? example +??? example "Example: CMake with the cget toolchain" 1. Create the following files: ```cmake title="CMakeLists.txt" - --8<-- "integration/vcpkg/CMakeLists.txt" + --8<-- "integration/cget/CMakeLists.txt" ``` ```cpp title="example.cpp" - --8<-- "integration/vcpkg/example.cpp" + --8<-- "integration/cget/example.cpp" ``` 2. Initialize cget @@ -443,6 +447,58 @@ installed by adding the `-DJSON_MultipleHeaders=ON` flag (i.e., `cget install nl - :octicons-file-24: File issues at the [library issue tracker](https://github.com/nlohmann/json/issues) - :octicons-question-24: [Xcode documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) +If you are using the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), add this +repository as a package dependency and depend on its `json` product: + +```swift +dependencies: [ + .package(url: "https://github.com/nlohmann/json", from: "3.12.0") +], +targets: [ + .target(name: "MyTarget", dependencies: [.product(name: "json", package: "json")]) +] +``` + +The library's own [`Package.swift`](https://github.com/nlohmann/json/blob/develop/Package.swift) publishes +`single_include/nlohmann` (not `single_include`) as the public headers directory, so include the header without the +`nlohmann/` prefix: + +```cpp +#include +``` + +??? example "Example: a minimal executable package" + + 1. Create the following files (the source file goes into `Sources/json_example/`, following Swift Package + Manager's directory layout convention): + + ```swift title="Package.swift" + --8<-- "integration/swift/Package.swift" + ``` + + ```cpp title="Sources/json_example/example.cpp" + --8<-- "integration/swift/example.cpp" + ``` + + 2. Build and run: + + ```shell + swift run --build-system native + ``` + +!!! warning + + On some toolchains, `swift run`/`swift build` fail to link an **executable** target against the header-only + `json` product with an error such as `Build input file cannot be found: '.../json.o'`, because the product + itself has no compiled sources; see [#4650](https://github.com/nlohmann/json/issues/4650) and the upstream + [Swift Package Manager issue](https://github.com/swiftlang/swift-package-manager/issues/5706). Passing + `--build-system native` (shown above) selects Swift Package Manager's legacy build system, which does not + have this problem; depending on the library from a *library* target instead of an executable is not affected + either. + +You can also add the dependency from within Xcode via **File → Add Package Dependencies…** and the same repository +URL; see [Apple's documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app). + ## NuGet !!! abstract "Summary" @@ -462,119 +518,28 @@ with dotnet add package nlohmann.json ``` -??? example +NuGet integrates with C++ projects through MSBuild, so it is mainly useful for Visual Studio/MSBuild projects; using +it as a dependency from other build systems, such as CMake, is possible but more cumbersome than the other package +managers on this page. - Probably the easiest way to use NuGet packages is through Visual Studio graphical interface. Right-click on a - project (any C++ project would do) in ā€œSolution Explorerā€ and select ā€œManage NuGet Packagesā€¦ā€ +??? example "Example: Visual Studio project" - ![](nuget/nuget-search-package.png) + 1. Right-click the project (any C++ project) in "Solution Explorer" and select "Manage NuGet Packages…" - Now you can click on ā€œBrowseā€ tab and find the package you like to install. + ![Right-clicking a project in Solution Explorer and selecting "Manage NuGet Packages…"](nuget/nuget-search-package.png) - ![](nuget/nuget-select-package.png) + 2. Switch to the "Browse" tab. - Most of the packages in NuGet gallery are .NET packages and would not be useful in a C++ project. Microsoft - recommends adding ā€œnativeā€ and ā€œnativepackageā€ tags to C++ NuGet packages to distinguish them, but even adding - ā€œnativeā€ to search query would still show many .NET-only packages in the list. - - Nevertheless, after finding the package you want, click on ā€œInstallā€ button and accept confirmation dialogs. - After the package is successfully added to the projects, you should be able to build and execute the project - without the need for making any more changes to build settings. + 3. Search for `nlohmann.json`, select it, and click "Install". - !!! note + ![Searching for and selecting the nlohmann.json package in the NuGet package manager](nuget/nuget-select-package.png) - A few notes: - - - NuGet packages are installed per project and not system-wide. The header and binaries for the package are only - available to the project it is added to, and not other projects (obviously unless we add the package to those - projects as well) - - One of the many great things about your elegant work is that it is a header-only library, which makes - deployment very straightforward. In case of libraries which need binary deployment (`.lib`, `.dll` and `.pdb` - for debug info) the different binaries for each supported compiler version must be added to the NuGet package. - Some library creators cram binary versions for all supported Visual C++ compiler versions in the same package, - so a single package will support all compilers. Some others create a different package for each compiler - version (and you usually see things like ā€œv140ā€ or ā€œvc141ā€ in package name to clarify which VC++ compiler this - package supports). - - Packages can have dependency to other packages, and in this case, NuGet will install all dependencies as well - as the requested package recursively. + 4. `#include ` in your code and build the project. The package's + `build/native/nlohmann.json.targets` file adds `$(MSBuildThisFileDirectory)include` to the project's + `AdditionalIncludeDirectories`, so no further include path configuration is needed. - **What happens behind the scenes** - - After you add a NuGet package, three changes occur in the project source directory. Of course, we could make these - changes manually instead of using GUI: - - ![](nuget/nuget-project-changes.png) - - 1. A `packages.config` file will be created (or updated to include the package name if one such file already - exists). This file contains a list of the packages required by this project (name and minimum version) and must - be added to the project source code repository, so if you move the source code to a new machine, MSBuild/NuGet - knows which packages it has to restore (which it does automatically before each build). - - ```xml - - - - - ``` - - 2. A `packages` folder which contains actual files in the packages (these are header and binary files required for - a successful build, plus a few metadata files). In case of this library for example, it contains `json.hpp`: - - ![](nuget/nuget-package-content.png) - - !!! note - - This directory should not be added to the project source code repository, as it will be restored before each - build by MSBuild/NuGet. If you go ahead and delete this folder, then build the project again, it will - magically re-appear! - - 3. Project MSBuild makefile (which for Visual C++ projects has a .vcxproj extension) will be updated to include - settings from the package. - - ![](nuget/nuget-project-makefile.png) - - The important bit for us here is line 170, which tells MSBuild to import settings from - `packages\nlohmann.json.3.5.0\build\native\nlohmann.json.targets` file. This is a file the package creator - created and added to the package (you can see it is one of the two files I created in this repository, the other - just contains package attributes like name and version number). What does it contain? - - For our header-only repository, the only setting we need is to add our include directory to the list of - `AdditionalIncludeDirectories`: - - ```xml - - - - - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories) - - - - ``` - - For libraries with binary files, we will need to add `.lib` files to linker inputs and add settings to copy - `.dll` and other redistributable files to output directory, if needed. - - There are other changes to the makefile as well: - - - Lines 165-167 add the `packages.config` as one of project files (so it is shown in Solution Explorer tree - view). It is added as None (no build action) and removing it wouldn’t affect build. - - - Lines 172-177 check to ensure the required packages are present. This will display a build error if package - directory is empty (for example when NuGet cannot restore packages because Internet connection is down). - Again, if you omit this section, the only change in build would be a more cryptic error message if build - fails. - - !!! note - - Changes to .vcxproj makefile should also be added to project source code repository. - - As you can see, the mechanism NuGet uses to modify project settings is through MSBuild makefiles, so using NuGet - with other build systems and compilers (like CMake) as a dependency manager is either impossible or more problematic - than useful. - -Please refer to [this extensive description](https://github.com/nlohmann/json/issues/1132#issuecomment-452250255) for -more information. +For further details, see the [original discussion](https://github.com/nlohmann/json/issues/1132#issuecomment-452250255) +this section is based on. ## Conda @@ -582,7 +547,7 @@ more information. package: [**`nlohmann_json`**](https://anaconda.org/conda-forge/nlohmann_json) - - ![](https://img.shields.io/conda/v/conda-forge/nlohmann_json) + - [![Conda package](https://img.shields.io/conda/v/conda-forge/nlohmann_json)](https://anaconda.org/conda-forge/nlohmann_json) - :octicons-tag-24: Available versions: current and previous versions - :octicons-rocket-24: The package is updated with every release. - :octicons-file-24: File issues at the [feedstock's issue tracker](https://github.com/conda-forge/nlohmann_json-feedstock/issues) @@ -595,7 +560,7 @@ If you are using [conda](https://conda.io/), you can use the package conda install -c conda-forge nlohmann_json ``` -??? example +??? example "Example: Raw compilation" 1. Create the following file: @@ -624,14 +589,37 @@ conda install -c conda-forge nlohmann_json ## MSYS2 -If you are using [MSYS2](http://www.msys2.org/), you can use the [mingw-w64-nlohmann-json](https://packages.msys2.org/base/mingw-w64-nlohmann-json) package, type `pacman -S mingw-w64-i686-nlohmann-json` or `pacman -S mingw-w64-x86_64-nlohmann-json` for installation. Please file issues [here](https://github.com/msys2/MINGW-packages/issues/new?title=%5Bnlohmann-json%5D) if you experience problems with the packages. +!!! abstract "Summary" -[![MSYS2 clang64 package](https://repology.org/badge/version-for-repo/msys2_clang64/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) -[![MSYS2 clangarm64 package](https://repology.org/badge/version-for-repo/msys2_clangarm64/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) -[![MSYS2 mingw package](https://repology.org/badge/version-for-repo/msys2_mingw/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) -[![MSYS2 ucrt64 package](https://repology.org/badge/version-for-repo/msys2_ucrt64/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) + package: [**`mingw-w64-nlohmann-json`**](https://packages.msys2.org/base/mingw-w64-nlohmann-json) -:material-update: The [package](https://packages.msys2.org/base/mingw-w64-nlohmann-json) is updated automatically. + - [![MSYS2 package](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpackages.msys2.org%2Fapi%2Fsearch%3Fquery%3Dnlohmann-json%26qtype%3Dpkg&query=%24.results.exact.version&label=msys2&prefix=v)](https://packages.msys2.org/base/mingw-w64-nlohmann-json) + - :octicons-rocket-24: The [package](https://packages.msys2.org/base/mingw-w64-nlohmann-json) is updated automatically. + - :octicons-file-24: File issues at the [MINGW-packages issue tracker](https://github.com/msys2/MINGW-packages/issues/new?title=%5Bnlohmann-json%5D) + - :octicons-question-24: [MSYS2 website](http://www.msys2.org/) + +If you are using [MSYS2](http://www.msys2.org/), you can use the [mingw-w64-nlohmann-json](https://packages.msys2.org/base/mingw-w64-nlohmann-json) +package; type `pacman -S mingw-w64-i686-nlohmann-json` or `pacman -S mingw-w64-x86_64-nlohmann-json` for installation. + +??? example "Example: Raw compilation" + + 1. Create the following file: + + ```cpp title="example.cpp" + --8<-- "integration/msys2/example.cpp" + ``` + + 2. Install the package (from an MSYS2 MinGW 64-bit shell): + + ```shell + pacman -S mingw-w64-x86_64-nlohmann-json + ``` + + 3. Compile the code: + + ```shell + g++ example.cpp -std=c++11 -o example + ``` ## MacPorts @@ -639,7 +627,7 @@ If you are using [MSYS2](http://www.msys2.org/), you can use the [mingw-w64-nloh port: [**`nlohmann-json`**](https://ports.macports.org/port/nlohmann-json/) - - [![MacPorts package](https://repology.org/badge/version-for-repo/macports/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) + - [![MacPorts package](https://img.shields.io/macports/v/nlohmann-json)](https://ports.macports.org/port/nlohmann-json/) - :octicons-tag-24: Available versions: current version - :octicons-rocket-24: The port is updated with every release. - :octicons-file-24: File issues at the [MacPorts issue tracker](https://trac.macports.org/newticket?port=nlohmann-json) @@ -841,7 +829,7 @@ If you are using [`CPM.cmake`](https://github.com/TheLartians/CPM.cmake), add th CPMAddPackage("gh:nlohmann/json@3.12.0") ``` -??? example +??? example "Example: CMake with `CPMAddPackage`" 1. Create the following files: @@ -878,7 +866,7 @@ CPMAddPackage("gh:nlohmann/json@3.12.0") - :octicons-file-24: File issues at the [xmake issue tracker](https://github.com/xmake-io/xmake-repo/issues) - :octicons-question-24: [xmake website](https://xmake.io/#/) -??? example +??? example "Example: xmake project" 1. Create the following files: @@ -906,18 +894,14 @@ CPMAddPackage("gh:nlohmann/json@3.12.0") ## Other package managers -The library is also contained in many other package repositories: [![Packaging status](https://repology.org/badge/tiny-repos/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) - -??? example "Package version overview" - - [![Packaging status](https://repology.org/badge/vertical-allrepos/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions) - +The library is also contained in many other package repositories; [Repology](https://repology.org/project/nlohmann-json/versions) tracks the packaged +versions across repositories. * * * ## Buckaroo -If you are using [Buckaroo](https://buckaroo.pm), you can install this library's module with `buckaroo add github.com/buckaroo-pm/nlohmann-json`. There is a demo repo [here](https://github.com/njlr/buckaroo-nholmann-json-example). +If you are using [Buckaroo](https://github.com/LoopPerfect/buckaroo), you can install this library's module with `buckaroo add github.com/buckaroo-pm/nlohmann-json`. There is a demo repo [here](https://github.com/njlr/buckaroo-nholmann-json-example). !!! warning @@ -928,7 +912,14 @@ If you are using [Buckaroo](https://buckaroo.pm), you can install this library's If you are using [CocoaPods](https://cocoapods.org), you can use the library by adding pod `"nlohmann_json", '~>3.1.2'` to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues -[here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open). +at [the repository](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/), as its issue tracker is no longer +reachable. + +[![CocoaPods package](https://img.shields.io/cocoapods/v/nlohmann_json)](https://cocoapods.org/pods/nlohmann_json) + +!!! warning + + The module is outdated as the respective [pod](https://cocoapods.org/pods/nlohmann_json) has not been updated in years. ## npm @@ -944,9 +935,3 @@ There is no official package published to the [ESP-IDF Component Registry](https new release and can be used as an unofficial component/package for ESP-IDF and PlatformIO projects. As the library is header-only, it can otherwise be used directly by adding its `include/` directory to your component's/project's include paths, like any other integration method described on this page. - -![](https://img.shields.io/cocoapods/v/nlohmann_json) - -!!! warning - - The module is outdated as the respective [pod](https://cocoapods.org/pods/nlohmann_json) has not been updated in years. diff --git a/docs/mkdocs/docs/integration/pkg-config.md b/docs/mkdocs/docs/integration/pkg-config.md index 429d0dea9..8b2e9047a 100644 --- a/docs/mkdocs/docs/integration/pkg-config.md +++ b/docs/mkdocs/docs/integration/pkg-config.md @@ -6,6 +6,9 @@ If you are using bare Makefiles, you can use `pkg-config` to generate the includ pkg-config nlohmann_json --cflags ``` +A pkg-config file is installed by [CMake](cmake.md#json_install) (when the `JSON_Install` option is enabled, which is +the default for a top-level build) as well as by several [package managers](package_managers.md). + Users of the [Meson build system](package_managers.md#meson) will also be able to use a system-wide library, which will be found by `pkg-config`: ```meson diff --git a/docs/mkdocs/docs/integration/swift/Package.swift b/docs/mkdocs/docs/integration/swift/Package.swift new file mode 100644 index 000000000..438ab8ccd --- /dev/null +++ b/docs/mkdocs/docs/integration/swift/Package.swift @@ -0,0 +1,17 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "json_example", + dependencies: [ + .package(url: "https://github.com/nlohmann/json", from: "3.12.0") + ], + targets: [ + .executableTarget( + name: "json_example", + dependencies: [ + .product(name: "json", package: "json") + ] + ) + ] +) diff --git a/docs/mkdocs/docs/integration/swift/example.cpp b/docs/mkdocs/docs/integration/swift/example.cpp new file mode 100644 index 000000000..ad0a06827 --- /dev/null +++ b/docs/mkdocs/docs/integration/swift/example.cpp @@ -0,0 +1,10 @@ +#include +#include +#include + +using json = nlohmann::json; + +int main() +{ + std::cout << std::setw(4) << json::meta() << std::endl; +} diff --git a/docs/mkdocs/hooks/unreleased_versions.py b/docs/mkdocs/hooks/unreleased_versions.py new file mode 100644 index 000000000..3f88b73f6 --- /dev/null +++ b/docs/mkdocs/hooks/unreleased_versions.py @@ -0,0 +1,82 @@ +"""Mark version numbers newer than the latest release with an "unreleased" badge.""" + +# The documentation is published from the develop branch and already describes the next release ("Added in version +# 3.13.0."). Every "version X.Y.Z" newer than the version in include/nlohmann/detail/abi_macros.hpp (which is only +# bumped when a release is made) gets a badge, so readers of a released version can tell which features they do not +# have yet; after a release, the badges disappear. Fenced and inline code, headings (a badge would change their +# anchor), and admonition/tab titles are left untouched, and statements about the future ("will be removed in version +# 4.0.0") are skipped. copy_markdown_source.py copies the raw source, so the *.md copies are unaffected. + +import logging +import os +import re + +log = logging.getLogger("mkdocs.hooks.unreleased_versions") + +_HEADER = os.path.join("..", "..", "include", "nlohmann", "detail", "abi_macros.hpp") # relative to mkdocs.yml +_VERSION_MACRO = re.compile(r"^#define NLOHMANN_JSON_VERSION_(MAJOR|MINOR|PATCH) (\d+)", re.MULTILINE) +_MENTION = re.compile(r"\b[Vv]ersion\s+(\d+)\.(\d+)\.(\d+)\b") +_FUTURE = re.compile(r"\b(?:will|planned|ahead of|until)\b[^.;:!?]*$", re.IGNORECASE) +_FENCE = re.compile(r"^\s*(`{3,}|~{3,})") +_NO_BADGE = re.compile(r"^\s*(?:#{1,6}(?:\s|$)|]|(?:!!!|\?\?\?\+?|===)\s)") +_NEW_BLOCK = re.compile(r"^\s*(?:[-*+]|\d+\.)\s") +_INLINE_CODE = re.compile(r"(`+).+?\1") + +_released = None +_badge = "" + + +def on_config(config): + global _released, _badge + path = os.path.join(os.path.dirname(config.config_file_path), _HEADER) + try: + with open(path, encoding="utf-8") as header: + parts = dict(_VERSION_MACRO.findall(header.read())) + _released = (int(parts["MAJOR"]), int(parts["MINOR"]), int(parts["PATCH"])) + except (OSError, KeyError) as error: + _released = None + log.info(f"not marking unreleased versions: cannot read {path} ({error})") # info: must not break --strict + return + version = ".".join(map(str, _released)) + _badge = (f' unreleased') + + +def on_page_markdown(markdown, *, page, config, files): + if _released is None: + return markdown + lines, fence, context = [], None, "" + for line in markdown.split("\n"): + original = line + match = _FENCE.match(line) + if fence: + if match and line.strip() == match.group(1) and match.group(1)[0] == fence[0] \ + and len(match.group(1)) >= len(fence): + fence = None + elif match: + fence = match.group(1) + elif not _NO_BADGE.match(line): + line = _mark_line(line, "" if _NEW_BLOCK.match(line) else context) + lines.append(line) + # the previous line catches statements like "will be removed in\nversion 4.0.0" + context = original if original.strip() else "" + return "\n".join(lines) + + +def _mark_line(line, context): + result, position = [], 0 + for code in _INLINE_CODE.finditer(line): + result.append(_mark_text(line[position:code.start()], context + " " + line[:position])) + result.append(code.group(0)) + position = code.end() + result.append(_mark_text(line[position:], context + " " + line[:position])) + return "".join(result) + + +def _mark_text(text, before): + def badge(match): + version = tuple(int(part) for part in match.groups()) + if version <= _released or _FUTURE.search(before + text[:match.start()]): + return match.group(0) + return match.group(0) + _badge + return _MENTION.sub(badge, text) diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 4bd207e60..b4daf34c3 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -85,12 +85,14 @@ nav: - features/modules.md - 'nlohmann Namespace': features/namespace.md - features/object_order.md + - features/performance.md - Parsing: - features/parsing/index.md - features/parsing/json_lines.md - features/parsing/parse_exceptions.md - features/parsing/parser_callbacks.md - features/parsing/sax_interface.md + - features/parsing/untrusted_input.md - features/assertions.md - features/serialization.md - features/enum_conversion.md @@ -233,6 +235,8 @@ nav: - '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md - 'clear_subtype': api/byte_container_with_subtype/clear_subtype.md - 'has_subtype': api/byte_container_with_subtype/has_subtype.md + - 'operator==': api/byte_container_with_subtype/operator_eq.md + - 'operator!=': api/byte_container_with_subtype/operator_ne.md - 'set_subtype': api/byte_container_with_subtype/set_subtype.md - 'subtype': api/byte_container_with_subtype/subtype.md - adl_serializer: @@ -249,6 +253,7 @@ nav: - 'operator string_t': api/json_pointer/operator_string_t.md - 'operator==': api/json_pointer/operator_eq.md - 'operator!=': api/json_pointer/operator_ne.md + - 'operator<=>': api/json_pointer/operator_spaceship.md - 'operator/': api/json_pointer/operator_slash.md - 'operator/=': api/json_pointer/operator_slasheq.md - 'parent_pointer': api/json_pointer/parent_pointer.md @@ -288,7 +293,7 @@ nav: - 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md - 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md - 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md - - 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md + - 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20, JSON_HAS_CPP_23, JSON_HAS_CPP_26': api/macros/json_has_cpp_11.md - 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md - 'JSON_HAS_RANGES': api/macros/json_has_ranges.md - 'JSON_HAS_STATIC_RTTI': api/macros/json_has_static_rtti.md @@ -380,8 +385,21 @@ markdown_extensions: auto_append: - ../includes/glossary.md +# report broken links, anchors, and nav entries as warnings, so that `mkdocs build --strict` (make build) fails +validation: + nav: + omitted_files: warn + not_found: warn + absolute_links: warn + links: + not_found: warn + anchors: warn + absolute_links: warn + unrecognized_links: warn + hooks: - hooks/copy_markdown_source.py + - hooks/unreleased_versions.py plugins: - search: @@ -389,7 +407,8 @@ plugins: lang: en - minify: minify_html: true - - git-revision-date-localized + - git-revision-date-localized: + strict: false # log "has no git logs" for uncommitted pages as info, not as a warning - redirects: redirect_maps: 'api/basic_json/operator_gtgt.md': api/operator_gtgt.md @@ -400,9 +419,25 @@ plugins: 'home/code_of_conduct.md': community/code_of_conduct.md - htmlproofer: # see https://github.com/manuzhang/mkdocs-htmlproofer-plugin enabled: !ENV [ENABLED_HTMLPROOFER, False] + raise_error_after_finish: true # log every broken link, then fail + skip_downloads: true # check headers only (customers.md links large PDFs) + raise_error_excludes: # integer status codes, fnmatch patterns + 403: ['*'] # bot protection against the plugin's "Bot " user agent + 429: ['*'] # rate limiting (hundreds of github.com URLs) + 502: ['*'] + 503: ['*'] + 504: ['*'] # timeouts are reported as 504 + -1: # connection errors + - 'https://repology.org/*' # repology.org is suspended since 2026-09 + - 'http://2ak5ape.257.cz/' # customers.md: kept although unreachable + 401: ['https://fossies.org/*'] # answers the plugin's user agent with 401, browsers and curl with 200 + 404: + - 'https://gitlab.b-data.ch/*' # answers the plugin's user agent with 404, browsers with 200 + # customers.md: kept although dead + - 'https://www.cisco.com/c/dam/en_us/about/doing_business/open_source/docs/CiscoWebexDeskCamera-23-1622100417.pdf' + - 'https://docs.cyberark.com/Downloads/Legal/Privileged%20Session%20Manager%20for%20SSH%20Third-Party%20Notices.pdf' + 500: ['https://marne.io/licenses'] # customers.md: kept although dead ignore_urls: - - http://nlohmann.github.io/json/* - - https://nlohmann.github.io/json/* - mailto:* - privacy: # repology.org refuses requests from GitHub Actions runners, which made diff --git a/docs/mkdocs/scripts/check_structure.py b/docs/mkdocs/scripts/check_structure.py index 78ad0d9fa..dd76dbab0 100755 --- a/docs/mkdocs/scripts/check_structure.py +++ b/docs/mkdocs/scripts/check_structure.py @@ -4,6 +4,7 @@ import glob import os.path import re import sys +import urllib.parse import yaml @@ -152,7 +153,7 @@ def check_structure() -> None: def check_examples() -> None: - example_files = sorted(glob.glob("../../examples/*.cpp")) + example_files = sorted(glob.glob("examples/*.cpp")) markdown_files = sorted(glob.glob("**/*.md", recursive=True)) # check if every example file is used in at least one markdown file @@ -211,11 +212,122 @@ def check_links() -> None: report("nav/duplicate_files", "mkdocs.yml", f'file "{duplicate_file}" is linked with multiple keys in "nav": {file_list_str}; only one is rendered properly, see #4564') +FENCE_RE = re.compile(r"^\s*(`{3,}|~{3,})") +INLINE_CODE_RE = re.compile(r"(`+).+?\1") + + +def markdown_lines(file): + """Yield (lineno, line) for all lines outside fenced code blocks.""" + fence = None + with open(file, encoding="utf-8") as content: + for lineno, line in enumerate(content, 1): + line = line.rstrip("\n") + match = FENCE_RE.match(line) + if fence is None: + if match: + fence = match.group(1) + else: + yield lineno, line + elif match and line.strip() == match.group(1) and match.group(1)[0] == fence[0] \ + and len(match.group(1)) >= len(fence): + fence = None + + +def check_example_titles() -> None: + """On API pages with more than one example, every example needs a title of the form "Example: ...".""" + example_re = re.compile(r'^\s*(?:\?\?\?\+?|!!!) example(?: "(.*)")?\s*$') + for file in sorted(glob.glob("api/**/*.md", recursive=True)): + examples = [(lineno, m.group(1)) for lineno, line in markdown_lines(file) if (m := example_re.match(line))] + if len(examples) < 2: + continue + for lineno, title in examples: + if title is None or not title.startswith("Example: "): + report("style/example_title", f"{file}:{lineno}", + f'pages with several examples need titles like "Example: ..." (found: {title!r})') + + +def check_heading_levels() -> None: + """Headings start at level 1 and never skip a level.""" + heading_re = re.compile(r"^(#{1,6})\s|^]") + for file in sorted(glob.glob("**/*.md", recursive=True)): + previous = 0 + for lineno, line in markdown_lines(file): + match = heading_re.match(line) + if not match: + continue + level = len(match.group(1)) if match.group(1) else int(match.group(2)) + if previous == 0 and level != 1: + report("structure/heading_level", f"{file}:{lineno}", f"first heading should have level 1, not {level}") + elif level > previous + 1 and previous != 0: + report("structure/heading_level", f"{file}:{lineno}", f"heading level jumps from {previous} to {level}") + previous = level + + +def check_image_alt_text() -> None: + """Images need an alternative text.""" + empty_alt_re = re.compile(r"!\[\s*\][(\[]") + img_re = re.compile(r"]*>", re.IGNORECASE) + alt_re = re.compile(r'\balt\s*=\s*"[^"]*\S[^"]*"', re.IGNORECASE) + for file in sorted(glob.glob("**/*.md", recursive=True)): + for lineno, line in markdown_lines(file): + line = INLINE_CODE_RE.sub("", line) + if empty_alt_re.search(line) or any(not alt_re.search(tag) for tag in img_re.findall(line)): + report("style/image_alt_text", f"{file}:{lineno}", "image without alternative text") + + +def check_header_links() -> None: + """Links to the documentation in the library's headers point to existing pages.""" + url_re = re.compile(r"https://json\.nlohmann\.me/([^\s#)>\"']*)") + for header in sorted(glob.glob("../../../include/nlohmann/**/*.hpp", recursive=True)): + with open(header, encoding="utf-8") as content: + for lineno, line in enumerate(content, 1): + for match in url_re.finditer(line): + path = urllib.parse.unquote(match.group(1)).strip("/") + if path and not (os.path.isfile(f"{path}.md") or os.path.isfile(f"{path}/index.md")): + report("links/header_link", f"{os.path.relpath(header, '../../..')}:{lineno}", + f'link to "{match.group(0)}" does not point to a documentation page') + + +def check_docset() -> None: + """Every API page and every macro has an entry in the docset index; no entry points to a missing page.""" + entry_re = re.compile(r"VALUES \('((?:[^']|'')*)', '(\w+)', '([^']*)'\);") + names_by_path = {} + with open("../../docset/docSet.sql", encoding="utf-8") as sql: + for name, _, path in entry_re.findall(sql.read()): + names_by_path.setdefault(path, set()).add(name.replace("''", "'")) + + def to_path(page): + if os.path.basename(page) == "index.md": + return page[:-len("index.md")] + "index.html" + return page[:-len(".md")] + "/index.html" + + pages = sorted(glob.glob("**/*.md", recursive=True)) + for path in sorted(set(names_by_path) - {to_path(p) for p in pages}): + report("docset/stale_entry", "../../docset/docSet.sql", f'entry "{path}" has no documentation page') + for page in (p for p in pages if p.startswith("api/")): + names = names_by_path.get(to_path(page)) + if not names: + report("docset/missing_entry", page, "page has no entry in docs/docset/docSet.sql") + elif page.startswith("api/macros/") and os.path.basename(page) != "index.md": + with open(page, encoding="utf-8") as content: + text = content.read() + match = re.search(r"^# (.+)$", text, re.MULTILINE) or re.search(r"

(.*?)

", text, re.DOTALL) + title = re.sub(r"<[^>]+>|\s+", " ", match.group(1)) + for macro in filter(None, (x.strip() for x in re.split(r"[,/]", title))): + if macro not in names: + report("docset/missing_macro", page, f'macro "{macro}" has no entry in docs/docset/docSet.sql') + + if __name__ == "__main__": print(120 * "-") check_structure() check_examples() check_links() + check_example_titles() + check_heading_levels() + check_image_alt_text() + check_header_links() + check_docset() print(120 * "-") if warnings > 0: diff --git a/docs/mkdocs/scripts/check_version_history.py b/docs/mkdocs/scripts/check_version_history.py new file mode 100644 index 000000000..81d629500 --- /dev/null +++ b/docs/mkdocs/scripts/check_version_history.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +"""Check the "Added in version" entries of the macro pages against the git tags. + +For every macro documented in docs/api/macros, find the first release tag whose amalgamated header mentions the macro +and compare it with the version the page's "Version history" names. A macro documented as added *before* it appears +in any release, or documented with a released version although no release contains it, is reported as a problem. A +macro that appears in the header *before* its documented version is only a note: many macros existed internally before +they were documented for users. The check is heuristic and meant to be run by hand, not in CI. + +usage: python3 check_version_history.py (from docs/mkdocs/docs, needs the git tags) +""" + +import functools +import glob +import re +# the script only runs git with fixed arguments and without a shell +import subprocess # nosec B404 +import sys + +HEADER_PATHS = ["single_include/nlohmann/json.hpp", "src/json.hpp"] # older releases used src/json.hpp +VERSION_RE = re.compile(r"[Aa]dded in (?:version )?(\d+)\.(\d+)\.(\d+)") +NAMED_VERSION_RE = re.compile(r"[Aa]dded `([A-Z0-9_]+)` in (?:version )?(\d+)\.(\d+)\.(\d+)") + + +def release_tags(): + # fixed git command without a shell + tags = subprocess.run(["git", "tag", "-l", "v*"], capture_output=True, text=True, check=True).stdout.split() # nosec B603, B607 + versions = [] + for tag in tags: + match = re.fullmatch(r"v(\d+)\.(\d+)\.(\d+)", tag) + if match: + versions.append((tuple(map(int, match.groups())), tag)) + return sorted(versions) + + +@functools.lru_cache(maxsize=None) +def header(tag): + for path in HEADER_PATHS: + # fixed git command without a shell; the tag names come from "git tag" + result = subprocess.run(["git", "show", f"{tag}:{path}"], capture_output=True, text=True) # nosec B603, B607 + if result.returncode == 0: + return result.stdout + return "" + + +def macros_and_versions(page): + with open(page, encoding="utf-8") as content: + text = content.read() + match = re.search(r"^# (.+)$", text, re.MULTILINE) or re.search(r"

(.*?)

", text, re.DOTALL) + title = re.sub(r"<[^>]+>|\s+", " ", match.group(1)) + macros = [x.strip() for x in re.split(r"[,/]", title) if x.strip()] + history = text.split("## Version history", 1)[-1] + entries = re.split(r"\n(?=\s*(?:\d+\.|-)\s)", history) + specific = {} # entries like "Added `JSON_HAS_CPP_23` in version 3.12.0." + general = [] + for entry in entries: + named = NAMED_VERSION_RE.search(entry) + if named: + specific[named.group(1)] = tuple(map(int, named.groups()[1:])) + continue + match = VERSION_RE.search(entry) + if match: + general.append(tuple(map(int, match.groups()))) + rest = [macro for macro in macros if macro not in specific] + if len(general) == len(rest): # numbered history: one entry per macro, in title order + pairs = list(zip(rest, general)) + else: + pairs = [(macro, general[0]) for macro in rest] if general else [] + return pairs + sorted(specific.items()) + + +def main(): + tags = release_tags() + latest = tags[-1][0] + problems = notes = 0 + for page in sorted(glob.glob("api/macros/*.md")): + if page.endswith("index.md"): + continue + for macro, documented in macros_and_versions(page): + pattern = re.compile(rf"\b{re.escape(macro)}\b") + first = next((version for version, tag in tags if pattern.search(header(tag))), None) + fmt = ".".join + if first is None: + if documented <= latest: + problems += 1 + print(f"{page}: {macro} is documented as added in {fmt(map(str, documented))}, " + f"but no release up to {fmt(map(str, latest))} contains it") + elif documented < first: + problems += 1 + print(f"{page}: {macro} is documented as added in {fmt(map(str, documented))}, " + f"but first appears in {fmt(map(str, first))}") + elif documented > first: + notes += 1 + print(f"{page}: note: {macro} is documented as added in {fmt(map(str, documented))}, " + f"but is mentioned in the header since {fmt(map(str, first))}") + print(f"{problems} possible problem(s), {notes} note(s)") + return 1 if problems else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/mkdocs/scripts/mermaid/check_mermaid.mjs b/docs/mkdocs/scripts/mermaid/check_mermaid.mjs new file mode 100644 index 000000000..bb0f20722 --- /dev/null +++ b/docs/mkdocs/scripts/mermaid/check_mermaid.mjs @@ -0,0 +1,54 @@ +// Check that every Mermaid diagram in the documentation parses. +// +// MkDocs does not validate Mermaid diagrams; a syntax error only shows up as an error box in the browser. This script +// extracts every ```mermaid block from the Markdown files and runs it through mermaid.parse(), the same parser the +// site uses (Material for MkDocs loads mermaid@11). Mermaid needs a DOM (DOMPurify), so jsdom provides one; the globals +// must be set before Mermaid is imported, hence the dynamic import. +// +// usage: node check_mermaid.mjs + +import { readdirSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { JSDOM } from 'jsdom'; + +const { window } = new JSDOM('', { pretendToBeVisual: true }); +globalThis.window = window; +globalThis.document = window.document; +globalThis.DOMParser = window.DOMParser; +const { default: mermaid } = await import('mermaid'); +mermaid.initialize({ startOnLoad: false }); + +const docsDir = process.argv[2] ?? 'docs'; +const opening = /^(\s*)(`{3,}|~{3,})\s*mermaid\s*$/; +let diagrams = 0; +let errors = 0; + +for (const file of readdirSync(docsDir, { recursive: true }).filter((f) => f.endsWith('.md')).sort()) { + const lines = readFileSync(join(docsDir, file), 'utf8').split('\n'); + for (let i = 0; i < lines.length; ++i) { + const match = opening.exec(lines[i]); + if (!match) { + continue; + } + // strip the indentation of the opening fence from every line (blocks inside admonitions or lists), like + // pymdownx.superfences does + const [, indent, fence] = match; + const closing = new RegExp(`^\\s*\\${fence[0]}{${fence.length},}\\s*$`); + const body = []; + let j = i + 1; + for (; j < lines.length && !closing.test(lines[j]); ++j) { + body.push(lines[j].startsWith(indent) ? lines[j].slice(indent.length) : lines[j].trimStart()); + } + ++diagrams; + try { + await mermaid.parse(body.join('\n')); + } catch (error) { + ++errors; + console.log(`${join(docsDir, file)}:${i + 1}: ${String(error?.message ?? error).replaceAll('\n', '\n ')}`); + } + i = j; + } +} + +console.log(`checked ${diagrams} Mermaid diagrams, ${errors} invalid`); +process.exitCode = errors ? 1 : 0; diff --git a/docs/mkdocs/scripts/mermaid/package-lock.json b/docs/mkdocs/scripts/mermaid/package-lock.json new file mode 100644 index 000000000..7fbbf6226 --- /dev/null +++ b/docs/mkdocs/scripts/mermaid/package-lock.json @@ -0,0 +1,1619 @@ +{ + "name": "check-mermaid", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "check-mermaid", + "dependencies": { + "jsdom": "30.1.1", + "mermaid": "11.17.2" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz", + "integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-7.1.2.tgz", + "integrity": "sha512-99DHAnXDB5z6EEK+9GMpVI7Mw4oxj97dY5bpOzMnjADQWxI8rN6TvTduuFLUhUMlS7/CfVZ06tcZsus6cltnNw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.4.1", + "@csstools/css-color-parser": "^4.2.4", + "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-tokenizer": "^4.0.2", + "lru-cache": "^11.5.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.2.tgz", + "integrity": "sha512-lSWTBMjAcmu2xn5yEDU7jh6QDV+C8GEKtdJ4pIQhXh26RkKQ7S3FuQlt+zZkUbTdJ4d3XyV1kPI/G0zWXxqaqw==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.1.0", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.2.tgz", + "integrity": "sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.1.tgz", + "integrity": "sha512-EtC7SoN1j6J4E4DCwg5QgbO5TGxgxIA1RXqe+W+qUM+BUcezx9wT+/tiQ/WO2yCX4i5X+Cuf9ciJ22aP4UEwWw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-tokenizer": "^4.0.2" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.4.tgz", + "integrity": "sha512-DyefytAZ735mX4Dq/WcDAXFtXhaEFvme0ZS9tVEBAc2whxUthXr0R0L2rmEPm59SrMBkGrFzQviBXMs/UtnABQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.2", + "@csstools/css-calc": "^3.4.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.1", + "@csstools/css-tokenizer": "^4.0.2" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.1.tgz", + "integrity": "sha512-ShL8BqPfbKJrJiKFH0xBbN0i7Nrh9HXYRuF+pzyj93R/BL2YAsUxJeqANErzqM+0JGl7vjHp+3OIgd/DL69YIA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.2" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.14.tgz", + "integrity": "sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.2.tgz", + "integrity": "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.16.0.tgz", + "integrity": "sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz", + "integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^2.0.1", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz", + "integrity": "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz", + "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.9.tgz", + "integrity": "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, + "node_modules/bidi-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cytoscape": { + "version": "3.34.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.3.tgz", + "integrity": "sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dompurify": { + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", + "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/entities": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/fastdom": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz", + "integrity": "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==", + "license": "MIT", + "dependencies": { + "strictdom": "^1.0.1" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-7.0.0.tgz", + "integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.1.tgz", + "integrity": "sha512-FahmoPK5vbPc+jxV1iErMHmAZypCZ942NHF4+qqaWAuvaKKTBZxawnmAtrbGWLU7MtlxfqIP0qw6aSI+aWGtLg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^7.0.0", + "@asamuzakjp/dom-selector": "^9.2.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.13", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^7.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "tough-cookie": "^6.0.2", + "undici": "^8.10.2", + "w3c-xmlserializer": "^6.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/mermaid": { + "version": "11.17.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.2.tgz", + "integrity": "sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.2.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.34.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.21", + "dompurify": "^3.3.3", + "es-toolkit": "^1.45.1", + "fastdom": "1.0.12", + "katex": "^0.16.47", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strictdom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz", + "integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tldts": { + "version": "7.4.16", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.16.tgz", + "integrity": "sha512-QwBER5KMR86IIjpIiO7H/Z3IMJPsZ1A6RKPAqzTTgOyUQUSt9FdnKcqhTaJmkY6HVrgouZHZR0ncK5QxvmnQeg==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.16" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.16", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.16.tgz", + "integrity": "sha512-MDolfaSJtlSK5Y0A1xl3277ekubZwobpBjugknDizI9O5Rm60a1m8k4ICK+MRsCDzPygT81mp3BBf5RKDlFRfA==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/uuid": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", + "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-6.0.0.tgz", + "integrity": "sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.2.tgz", + "integrity": "sha512-TEZA+Zqxin7Jjsm2cjRohCmen5awh+hT6Zi3VZdqZlNRk7zvOI/9WpBFg/DWlA56bWnzwm6DuB8NS0EsxQH9uQ==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + } + } +} diff --git a/docs/mkdocs/scripts/mermaid/package.json b/docs/mkdocs/scripts/mermaid/package.json new file mode 100644 index 000000000..6cf40b9f6 --- /dev/null +++ b/docs/mkdocs/scripts/mermaid/package.json @@ -0,0 +1,10 @@ +{ + "name": "check-mermaid", + "private": true, + "description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)", + "type": "module", + "dependencies": { + "jsdom": "30.1.1", + "mermaid": "11.17.2" + } +} diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index c2943f645..7998dc6dd 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -54,8 +54,9 @@ using parser_callback_t = /*! @brief syntax analysis -This class implements an iterative parser that keeps the open containers on -an explicit stack and reports what it reads as SAX events. +This class implements a parser for JSON text. Nested arrays and objects are tracked with an explicit +stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read +is reported as SAX events. */ template class parser diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index f170de7dd..cf3720a25 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -78,7 +78,7 @@ class json_pointer } /// @brief return a string representation of the JSON pointer - /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/ + /// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/ JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string()) operator string_t() const { @@ -87,7 +87,7 @@ class json_pointer #ifndef JSON_NO_IO /// @brief write string representation of the JSON pointer to stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @sa https://json.nlohmann.me/api/operator_ltlt/ friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr) { o << ptr.to_string(); @@ -1099,7 +1099,8 @@ class json_pointer friend bool operator!=(const StringType& lhs, const json_pointer& rhs); - /// @brief compares two JSON pointer for less-than + /// @brief compares two JSON pointers for less-than + /// @sa https://json.nlohmann.me/api/json_pointer/operator_spaceship/ template // NOLINTNEXTLINE(readability-redundant-declaration) friend bool operator<(const json_pointer& lhs, diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 8850ca959..3a5f7e606 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -298,7 +298,7 @@ void templated_json_throw(ExceptionType exception) @brief macro to briefly define a mapping between an enum and JSON with exception on invalid input @def NLOHMANN_JSON_SERIALIZE_ENUM_STRICT -@since version 3.12.0 +@since version 3.13.0 */ #define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \ template \ diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index bbe310d9b..d0f779109 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -5128,7 +5128,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @{ #ifndef JSON_NO_IO /// @brief serialize to stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @sa https://json.nlohmann.me/api/operator_ltlt/ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { // read width member and use it as the indentation parameter if nonzero @@ -5147,7 +5147,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /// @brief serialize to stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @sa https://json.nlohmann.me/api/operator_ltlt/ /// @deprecated This function is deprecated since 3.0.0 and will be removed in /// version 4.0.0 of the library. Please use /// operator<<(std::ostream&, const basic_json&) instead; that is, @@ -5316,7 +5316,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #endif #ifndef JSON_NO_IO /// @brief deserialize from stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + /// @sa https://json.nlohmann.me/api/operator_gtgt/ /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in /// version 4.0.0 of the library. Please use /// operator>>(std::istream&, basic_json&) instead; that is, @@ -5328,7 +5328,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /// @brief deserialize from stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + /// @sa https://json.nlohmann.me/api/operator_gtgt/ friend std::istream& operator>>(std::istream& i, basic_json& j) { // parse into a temporary so that j is left unchanged if parsing fails diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 73079da57..5a6f5afd7 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2710,7 +2710,7 @@ void templated_json_throw(ExceptionType exception) @brief macro to briefly define a mapping between an enum and JSON with exception on invalid input @def NLOHMANN_JSON_SERIALIZE_ENUM_STRICT -@since version 3.12.0 +@since version 3.13.0 */ #define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \ template \ @@ -17904,8 +17904,9 @@ using parser_callback_t = /*! @brief syntax analysis -This class implements an iterative parser that keeps the open containers on -an explicit stack and reports what it reads as SAX events. +This class implements a parser for JSON text. Nested arrays and objects are tracked with an explicit +stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read +is reported as SAX events. */ template class parser @@ -19646,7 +19647,7 @@ class json_pointer } /// @brief return a string representation of the JSON pointer - /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/ + /// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/ JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string()) operator string_t() const { @@ -19655,7 +19656,7 @@ class json_pointer #ifndef JSON_NO_IO /// @brief write string representation of the JSON pointer to stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @sa https://json.nlohmann.me/api/operator_ltlt/ friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr) { o << ptr.to_string(); @@ -20667,7 +20668,8 @@ class json_pointer friend bool operator!=(const StringType& lhs, const json_pointer& rhs); - /// @brief compares two JSON pointer for less-than + /// @brief compares two JSON pointers for less-than + /// @sa https://json.nlohmann.me/api/json_pointer/operator_spaceship/ template // NOLINTNEXTLINE(readability-redundant-declaration) friend bool operator<(const json_pointer& lhs, @@ -31825,7 +31827,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @{ #ifndef JSON_NO_IO /// @brief serialize to stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @sa https://json.nlohmann.me/api/operator_ltlt/ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { // read width member and use it as the indentation parameter if nonzero @@ -31844,7 +31846,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /// @brief serialize to stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @sa https://json.nlohmann.me/api/operator_ltlt/ /// @deprecated This function is deprecated since 3.0.0 and will be removed in /// version 4.0.0 of the library. Please use /// operator<<(std::ostream&, const basic_json&) instead; that is, @@ -32013,7 +32015,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #endif #ifndef JSON_NO_IO /// @brief deserialize from stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + /// @sa https://json.nlohmann.me/api/operator_gtgt/ /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in /// version 4.0.0 of the library. Please use /// operator>>(std::istream&, basic_json&) instead; that is, @@ -32025,7 +32027,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /// @brief deserialize from stream - /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + /// @sa https://json.nlohmann.me/api/operator_gtgt/ friend std::istream& operator>>(std::istream& i, basic_json& j) { // parse into a temporary so that j is left unchanged if parsing fails From 6d0867a85f0148ae184f457ec6755f9d4e184132 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 2 Oct 2026 17:25:17 +0200 Subject: [PATCH 06/27] Make comparisons with scalars noexcept only when the conversion is (#5751) The comparison operators taking a scalar (==, !=, <, <=, >, >=, and C++20's <=>) convert the scalar to a basic_json and compare, but were unconditionally noexcept. When that conversion throws, the program called std::terminate instead of propagating the exception, e.g. when comparing a json with a string literal under memory pressure (std::bad_alloc) or with an enum value not mapped by NLOHMANN_JSON_SERIALIZE_ENUM_STRICT (out_of_range.410). clang-tidy 22.1 reports the latter as bugprone-exception-escape. Declare the 16 scalar overloads noexcept(std::is_nothrow_constructible::value): they stay noexcept for numbers, Booleans, nullptr, and plain enums, and are noexcept(false) for strings and enums whose to_json may throw. The comparisons of two basic_json values are unchanged. Restore the strict-enum comparisons removed from unit-conversions.cpp in the previous PR, check that comparing an unmapped strict enum now throws, and pin the new exception specifications in unit-noexcept.cpp. Document the exception safety of overload (2) on all seven operator pages. Ran make amalgamate. Signed-off-by: Niels Lohmann --- .../mkdocs/docs/api/basic_json/operator_eq.md | 14 +++++--- .../mkdocs/docs/api/basic_json/operator_ge.md | 12 +++++-- .../mkdocs/docs/api/basic_json/operator_gt.md | 12 +++++-- .../mkdocs/docs/api/basic_json/operator_le.md | 12 +++++-- .../mkdocs/docs/api/basic_json/operator_lt.md | 12 +++++-- .../mkdocs/docs/api/basic_json/operator_ne.md | 12 +++++-- .../docs/api/basic_json/operator_spaceship.md | 10 ++++-- include/nlohmann/json.hpp | 32 +++++++++---------- single_include/nlohmann/json.hpp | 32 +++++++++---------- tests/src/unit-conversions.cpp | 21 ++++++++++++ tests/src/unit-noexcept.cpp | 9 ++++++ 11 files changed, 125 insertions(+), 53 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/operator_eq.md b/docs/mkdocs/docs/api/basic_json/operator_eq.md index 26eda720f..58a7e74da 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json/operator_eq.md @@ -5,17 +5,17 @@ bool operator==(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator==(const_reference lhs, const ScalarType rhs) noexcept; // (2) +bool operator==(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2) template -bool operator==(ScalarType lhs, const const_reference rhs) noexcept; // (2) +bool operator==(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2) // since C++20 class basic_json { bool operator==(const_reference rhs) const noexcept; // (1) template - bool operator==(ScalarType rhs) const noexcept; // (2) + bool operator==(ScalarType rhs) const noexcept(/* see below */); // (2) }; ``` @@ -46,7 +46,12 @@ whether the values `lhs`/`*this` and `rhs` are equal ## Exception safety -No-throw guarantee: this function never throws exceptions. +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and + `#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion + throws, for example `std::bad_alloc` when converting a string, or + [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md). ## Complexity @@ -171,3 +176,4 @@ Linear. 1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. 2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. + Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`. diff --git a/docs/mkdocs/docs/api/basic_json/operator_ge.md b/docs/mkdocs/docs/api/basic_json/operator_ge.md index f7899beab..99949f226 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ge.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ge.md @@ -5,10 +5,10 @@ bool operator>=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator>=(const_reference lhs, const ScalarType rhs) noexcept; // (2) +bool operator>=(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2) template -bool operator>=(ScalarType lhs, const const_reference rhs) noexcept; // (2) +bool operator>=(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2) ``` 1. Compares whether one JSON value `lhs` is greater than or equal to another JSON value `rhs` according to the following @@ -39,7 +39,12 @@ whether `lhs` is greater than or equal to `rhs` ## Exception safety -No-throw guarantee: this function never throws exceptions. +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and + `#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion + throws, for example `std::bad_alloc` when converting a string, or + [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md). ## Complexity @@ -94,3 +99,4 @@ Linear. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. + Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`. diff --git a/docs/mkdocs/docs/api/basic_json/operator_gt.md b/docs/mkdocs/docs/api/basic_json/operator_gt.md index 486da5fd0..3fcb339da 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_gt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_gt.md @@ -5,10 +5,10 @@ bool operator>(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator>(const_reference lhs, const ScalarType rhs) noexcept; // (2) +bool operator>(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2) template -bool operator>(ScalarType lhs, const const_reference rhs) noexcept; // (2) +bool operator>(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2) ``` 1. Compares whether one JSON value `lhs` is greater than another JSON value `rhs` according to the @@ -39,7 +39,12 @@ whether `lhs` is greater than `rhs` ## Exception safety -No-throw guarantee: this function never throws exceptions. +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and + `#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion + throws, for example `std::bad_alloc` when converting a string, or + [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md). ## Complexity @@ -84,3 +89,4 @@ Linear. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. + Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`. diff --git a/docs/mkdocs/docs/api/basic_json/operator_le.md b/docs/mkdocs/docs/api/basic_json/operator_le.md index 4334fe35e..db193d0db 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_le.md +++ b/docs/mkdocs/docs/api/basic_json/operator_le.md @@ -5,10 +5,10 @@ bool operator<=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator<=(const_reference lhs, const ScalarType rhs) noexcept; // (2) +bool operator<=(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2) template -bool operator<=(ScalarType lhs, const const_reference rhs) noexcept; // (2) +bool operator<=(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2) ``` 1. Compares whether one JSON value `lhs` is less than or equal to another JSON value `rhs` @@ -40,7 +40,12 @@ whether `lhs` is less than or equal to `rhs` ## Exception safety -No-throw guarantee: this function never throws exceptions. +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and + `#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion + throws, for example `std::bad_alloc` when converting a string, or + [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md). ## Complexity @@ -95,3 +100,4 @@ Linear. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. + Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`. diff --git a/docs/mkdocs/docs/api/basic_json/operator_lt.md b/docs/mkdocs/docs/api/basic_json/operator_lt.md index 118d817c8..1b8e65225 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_lt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_lt.md @@ -5,10 +5,10 @@ bool operator<(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator<(const_reference lhs, const ScalarType rhs) noexcept; // (2) +bool operator<(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2) template -bool operator<(ScalarType lhs, const const_reference rhs) noexcept; // (2) +bool operator<(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2) ``` 1. Compares whether one JSON value `lhs` is less than another JSON value `rhs` according to the @@ -49,7 +49,12 @@ whether `lhs` is less than `rhs` ## Exception safety -No-throw guarantee: this function never throws exceptions. +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and + `#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion + throws, for example `std::bad_alloc` when converting a string, or + [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md). ## Complexity @@ -94,3 +99,4 @@ Linear. 1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. 2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. + Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`. diff --git a/docs/mkdocs/docs/api/basic_json/operator_ne.md b/docs/mkdocs/docs/api/basic_json/operator_ne.md index a8c6fecc2..ceeb31ab3 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ne.md @@ -5,10 +5,10 @@ bool operator!=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; // (2) +bool operator!=(const_reference lhs, const ScalarType rhs) noexcept(/* see below */); // (2) template -bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; // (2) +bool operator!=(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2) ``` 1. Compares two JSON values for inequality. Returns `#!cpp !(lhs == rhs)`. @@ -36,7 +36,12 @@ whether the values `lhs`/`*this` and `rhs` are not equal ## Exception safety -No-throw guarantee: this function never throws exceptions. +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and + `#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion + throws, for example `std::bad_alloc` when converting a string, or + [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md). ## Complexity @@ -98,3 +103,4 @@ Linear. member function in version 3.13.0; since C++20, the compiler rewrites `a != b` using `operator==`. 2. Added in version 1.0.0. Changed in version 3.13.0 to remove special-casing for `NaN` and `discarded` values; `operator!=` now consistently means `!(a == b)`. Since C++20, the compiler rewrites `a != b` using `operator==`. + Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`. diff --git a/docs/mkdocs/docs/api/basic_json/operator_spaceship.md b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md index 9e91d0d2d..47ca22484 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_spaceship.md +++ b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md @@ -6,7 +6,7 @@ class basic_json { std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1) template - std::partial_ordering operator<=>(const ScalarType rhs) const noexcept; // (2) + std::partial_ordering operator<=>(const ScalarType rhs) const noexcept(/* see below */); // (2) }; ``` @@ -39,7 +39,12 @@ the `std::partial_ordering` of the 3-way comparison of `*this` and `rhs` ## Exception safety -No-throw guarantee: this function never throws exceptions. +1. No-throw guarantee: this function never throws exceptions. +2. No-throw guarantee if converting the scalar to a JSON value cannot throw, as for numbers, Booleans, and + `#!cpp nullptr`; the function is `#!cpp noexcept` exactly in that case. Otherwise, it throws what the conversion + throws, for example `std::bad_alloc` when converting a string, or + [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) for an enum value not mapped by + [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../macros/nlohmann_json_serialize_enum_strict.md). ## Complexity @@ -98,3 +103,4 @@ Linear. 1. Added in version 3.11.0. 2. Added in version 3.11.0. + Made conditionally `#!cpp noexcept` in version 3.13.0; before, a throwing conversion called `std::terminate`. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index d0f779109..bb78b211d 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4865,7 +4865,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template requires std::is_scalar_v - bool operator==(ScalarType rhs) const noexcept + bool operator==(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) { return *this == basic_json(rhs); } @@ -4888,7 +4888,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ template requires std::is_scalar_v - std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + std::partial_ordering operator<=>(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) // *NOPAD* { return *this <=> basic_json(rhs); // *NOPAD* } @@ -4913,7 +4913,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - bool operator<=(ScalarType rhs) const noexcept + bool operator<=(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) { return *this <= basic_json(rhs); } @@ -4934,7 +4934,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - bool operator>=(ScalarType rhs) const noexcept + bool operator>=(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) { return *this >= basic_json(rhs); } @@ -4959,7 +4959,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(const_reference lhs, ScalarType rhs) noexcept + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs == basic_json(rhs); } @@ -4968,7 +4968,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(ScalarType lhs, const_reference rhs) noexcept + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) == rhs; } @@ -4984,7 +4984,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs != basic_json(rhs); } @@ -4993,7 +4993,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) != rhs; } @@ -5013,7 +5013,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(const_reference lhs, ScalarType rhs) noexcept + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs < basic_json(rhs); } @@ -5022,7 +5022,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(ScalarType lhs, const_reference rhs) noexcept + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) < rhs; } @@ -5042,7 +5042,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs <= basic_json(rhs); } @@ -5051,7 +5051,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) <= rhs; } @@ -5072,7 +5072,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(const_reference lhs, ScalarType rhs) noexcept + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs > basic_json(rhs); } @@ -5081,7 +5081,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(ScalarType lhs, const_reference rhs) noexcept + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) > rhs; } @@ -5101,7 +5101,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs >= basic_json(rhs); } @@ -5110,7 +5110,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) >= rhs; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5a6f5afd7..c4f3313ae 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -31564,7 +31564,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template requires std::is_scalar_v - bool operator==(ScalarType rhs) const noexcept + bool operator==(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) { return *this == basic_json(rhs); } @@ -31587,7 +31587,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ template requires std::is_scalar_v - std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + std::partial_ordering operator<=>(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) // *NOPAD* { return *this <=> basic_json(rhs); // *NOPAD* } @@ -31612,7 +31612,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - bool operator<=(ScalarType rhs) const noexcept + bool operator<=(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) { return *this <= basic_json(rhs); } @@ -31633,7 +31633,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - bool operator>=(ScalarType rhs) const noexcept + bool operator>=(ScalarType rhs) const noexcept(std::is_nothrow_constructible::value) { return *this >= basic_json(rhs); } @@ -31658,7 +31658,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(const_reference lhs, ScalarType rhs) noexcept + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs == basic_json(rhs); } @@ -31667,7 +31667,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(ScalarType lhs, const_reference rhs) noexcept + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) == rhs; } @@ -31683,7 +31683,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs != basic_json(rhs); } @@ -31692,7 +31692,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) != rhs; } @@ -31712,7 +31712,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(const_reference lhs, ScalarType rhs) noexcept + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs < basic_json(rhs); } @@ -31721,7 +31721,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(ScalarType lhs, const_reference rhs) noexcept + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) < rhs; } @@ -31741,7 +31741,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs <= basic_json(rhs); } @@ -31750,7 +31750,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) <= rhs; } @@ -31771,7 +31771,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(const_reference lhs, ScalarType rhs) noexcept + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs > basic_json(rhs); } @@ -31780,7 +31780,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(ScalarType lhs, const_reference rhs) noexcept + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) > rhs; } @@ -31800,7 +31800,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) { return lhs >= basic_json(rhs); } @@ -31809,7 +31809,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) >= rhs; } diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index ff6e5e5c7..df8685529 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -1764,6 +1764,12 @@ TEST_CASE("Strict JSON to enum mapping") CHECK(json("herz").get() == strict_cards::herz); CHECK(json("karo").get() == strict_cards::karo); + // comparison of enum and json + CHECK(strict_cards::kreuz == json("kreuz")); + CHECK(strict_cards::pik == json("pik")); + CHECK(strict_cards::herz == json("herz")); + CHECK(strict_cards::karo == json("karo")); + // invalid json -> exception thrown json _; CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); @@ -1771,6 +1777,12 @@ TEST_CASE("Strict JSON to enum mapping") // conversion of unmapped enum -> exception thrown CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + // comparing an unmapped enum with json throws the same exception + // (the scalar comparison operators used to be noexcept, so this + // called std::terminate) + CHECK_THROWS_WITH_AS(static_cast(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + CHECK_THROWS_WITH_AS(static_cast(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + // invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the // message (regression test for #5667); such strings can reach get() unvalidated, // e.g. from from_cbor()/from_msgpack() (#5529) @@ -1792,12 +1804,21 @@ TEST_CASE("Strict JSON to enum mapping") CHECK(json("completed").get() == STRICT_TS_COMPLETED); CHECK(json().get() == STRICT_TS_INVALID); + // comparison of enum and json + CHECK(STRICT_TS_STOPPED == json("stopped")); + CHECK(STRICT_TS_RUNNING == json("running")); + CHECK(STRICT_TS_COMPLETED == json("completed")); + CHECK(STRICT_TS_INVALID == json()); + // invalid json -> exception thrown json _; CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&); // conversion of unmapped enum -> exception thrown CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); + + // comparing an unmapped enum with json throws the same exception + CHECK_THROWS_WITH_AS(static_cast(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); } } diff --git a/tests/src/unit-noexcept.cpp b/tests/src/unit-noexcept.cpp index 637915f24..85361df24 100644 --- a/tests/src/unit-noexcept.cpp +++ b/tests/src/unit-noexcept.cpp @@ -54,6 +54,15 @@ static_assert(noexcept(json(pod {})), ""); static_assert(noexcept(std::declval().get()), ""); static_assert(!noexcept(std::declval().get()), ""); static_assert(noexcept(json(pod{})), ""); + +// comparing with a scalar is noexcept exactly when converting the scalar is +static_assert(noexcept(std::declval() == 1), ""); +static_assert(noexcept(1 != std::declval()), ""); +static_assert(noexcept(std::declval() < 2.5), ""); +static_assert(noexcept(nullptr == std::declval()), ""); +static_assert(!noexcept(std::declval() == "foo"), ""); +static_assert(!noexcept("foo" >= std::declval()), ""); +static_assert(noexcept(std::declval() == std::declval()), ""); } // namespace TEST_CASE("noexcept") From 78ddd95794881a2830fa47972f8b6bcfba842c13 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:13 +0200 Subject: [PATCH 07/27] Document GCC < 11 incomplete-type error with optional members (#3669) (#5596) * Document GCC < 11 incomplete-type error with optional members (#3669) With GCC 10 and older in C++11/14 mode, a free to_json() for a type holding an optional (Dummy constructible from json) fails with "invalid use of incomplete type detector<...to_json_function...>". ADL for Dummy finds the unrelated to_json and closes an instantiation cycle through optional's converting constructor. The same error reproduces without the library, so it can't be fixed here. Add a FAQ entry explaining the cause and the hidden-friend workaround, recommend hidden friends in the arbitrary types docs, and add a regression test that keeps the workaround compiling on GCC 7-10. Signed-off-by: Niels Lohmann * Use the #3669 fixture's optional member in to_json Issue3669Holder::d is never read, so clang's -Weverything -Werror build (ci_test_clang) fails with -Wunused-private-field. Reference the member in the hidden-friend to_json; this does not affect the instantiation cycle the fixture reproduces. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/features/arbitrary_types.md | 1 + docs/mkdocs/docs/home/faq.md | 45 ++++++++++++++++ tests/src/unit-regression2.cpp | 54 ++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md index f7f3326ce..53f9cbd07 100644 --- a/docs/mkdocs/docs/features/arbitrary_types.md +++ b/docs/mkdocs/docs/features/arbitrary_types.md @@ -79,6 +79,7 @@ Some important things: * When using `get()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.) * In function `from_json`, use function [`at()`](../api/basic_json/at.md) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior. * You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these. +* If you control the type, consider defining `to_json`/`from_json` as `friend` functions inside the class ("hidden friends"). Argument-dependent lookup then only finds them for your type, which also avoids a [GCC < 11 compilation error](../home/faq.md#incomplete-detector-type-with-gcc-11). ??? example "Example: serialize a `person` to JSON with `to_json`" diff --git a/docs/mkdocs/docs/home/faq.md b/docs/mkdocs/docs/home/faq.md index ca113004c..852fd8ffd 100644 --- a/docs/mkdocs/docs/home/faq.md +++ b/docs/mkdocs/docs/home/faq.md @@ -305,6 +305,51 @@ Only very old NDKs (before r18), which defaulted to GCC and `gnustl`, lacked C++ `std::to_string`. If you run into this, update to a current NDK. +### Incomplete `detector` type with GCC < 11 + +!!! question + + Why does GCC 10 or older fail with `invalid use of incomplete type 'struct nlohmann::detail::detector<..., to_json_function, ...>'` for a type that holds an `optional` member? + +This happens with GCC 10 and older in C++11/C++14 mode when all of these hold: + +- a class `Holder` has an `optional` member (e.g., `boost::optional`), +- `Dummy` has a constructor taking a `json` value, and +- `to_json` for `Holder` is a free function in the namespace of `Dummy`. + +```cpp +class Dummy { + public: + explicit Dummy(const nlohmann::json& j); +}; + +class Holder { + boost::optional d; +}; + +void to_json(nlohmann::json& j, const Holder& h); // triggers the error +``` + +To decide whether `Dummy` is copyable, the compiler checks whether a `Dummy` can be converted to `json`. That check +looks up `to_json` via argument-dependent lookup, finds the unrelated `to_json` for `Holder`, and eventually asks again +whether `Dummy` is copyable. GCC before version 11 turns this cycle into a hard error; GCC 11 and later, Clang, and +C++17 mode compile the code. The same error shows up without this library whenever a constrained converting constructor +is involved, so the library can't avoid it. + +To work around this, define `to_json` (and `from_json`) as a *hidden friend* inside the class. That way, +argument-dependent lookup only finds it for `Holder`: + +```cpp +class Holder { + boost::optional d; + + friend void to_json(nlohmann::json& j, const Holder& h) { /* ... */ } +}; +``` + +The [`NLOHMANN_DEFINE_TYPE_INTRUSIVE`](../api/macros/nlohmann_define_type_intrusive.md) macros define hidden friends as +well. See [#3669](https://github.com/nlohmann/json/issues/3669) for details. + ### Missing STL function !!! question "Questions" diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 281c160af..1ea5ac595 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -233,6 +233,52 @@ class my_allocator : public std::allocator }; }; +///////////////////////////////////////////////////////////////////// +// for #3669 +///////////////////////////////////////////////////////////////////// + +// mimics boost::optional's converting constructor, whose SFINAE check asks +// whether T is constructible from const U& +template +struct issue3669_is_constructible +{ + template()))> + static char test(int); + template + static long test(...); + static constexpr bool value = sizeof(test(0)) == 1; +}; + +template +class issue3669_optional +{ + public: + issue3669_optional() = default; + template + issue3669_optional(const issue3669_optional& /*unused*/, // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + typename std::enable_if::value, bool>::type /*unused*/ = true) {} +}; + +class Issue3669Dummy +{ + public: + explicit Issue3669Dummy(const json& /*unused*/) {} +}; + +class Issue3669Holder +{ + issue3669_optional d{}; + + // GCC < 11 (C++11/14) rejects a free to_json(json&, const Issue3669Holder&) + // here, because ADL for Issue3669Dummy finds it and closes an instantiation + // cycle; a hidden friend is only visible to ADL for Issue3669Holder + friend void to_json(json& j, const Issue3669Holder& h) + { + static_cast(h.d); // silence -Wunused-private-field + j = "holder"; + } +}; + TEST_CASE("regression tests 2") { SECTION("issue #1001 - Fix memory leak during parser callback") @@ -762,6 +808,14 @@ TEST_CASE("regression tests 2") CHECK(j == k); } + SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json") + { + const Issue3669Holder h{}; + const Issue3669Holder h2(h); // NOLINT(performance-unnecessary-copy-initialization) + const json j = h2; + CHECK(j == "holder"); + } + } TEST_CASE("regression test - parser callback must not lose a duplicate key's prior value") From d11e89471bdf0223b4a97fbfb25d95e36c8cd91a Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:18 +0200 Subject: [PATCH 08/27] Assert on missing array indices in const operator[] and document the JSON pointer case (#5606) The const operator[] overloads are unchecked by design, and a missing key or index is undefined behavior. The key overload guards this with a runtime assertion, but the index overload did not, although the element access documentation says an assertion fires in both cases. The const JSON pointer overload inherits both through json_pointer::get_unchecked(), so a pointer to a missing array index read out of bounds even in debug builds, and its documentation promised out_of_range.404 for any pointer that cannot be resolved. Add JSON_ASSERT(idx < size()) to const operator[](size_type), which also covers the index leg of the const JSON pointer overload. Document the undefined behavior for the const JSON pointer overload in operator[].md and in the runtime assertions page. Release builds are unchanged. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/operator[].md | 14 +++++-- docs/mkdocs/docs/features/assertions.md | 38 +++++++++++++++---- include/nlohmann/detail/json_pointer.hpp | 10 ++++- include/nlohmann/json.hpp | 1 + single_include/nlohmann/json.hpp | 11 +++++- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index 23926d5f6..74996d629 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -89,6 +89,9 @@ Strong exception safety: if an exception occurs, the original value stays intact - Throws [`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) if an array index in the passed JSON pointer `ptr` exceeds the range of `size_type` (e.g., on 32-bit platforms). + For the **const** version, an object key or array index in `ptr` that does not exist is not reported by an + exception, but is undefined behavior (see the notes below). Use [`at`](at.md) for checked access. + ## Complexity 1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`. @@ -103,9 +106,12 @@ Strong exception safety: if an exception occurs, the original value stays intact The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element (see the notes below). - 1. If the element at index `idx` does not exist, the behavior is undefined. + 1. If the element at index `idx` does not exist, the behavior is undefined and is **guarded by a + [runtime assertion](../../features/assertions.md)**! 2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a [runtime assertion](../../features/assertions.md)**! + 3. If the JSON pointer `ptr` refers to an object key or an array index that does not exist, the behavior is + undefined and is **guarded by a [runtime assertion](../../features/assertions.md)**! 1. The non-const version may add values: If `idx` is beyond the range of the array (i.e., `idx >= size()`), then the array is silently filled up with `#!json null` values to make `idx` a valid reference to the last stored element. In @@ -273,9 +279,11 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Version history 1. Added in version 1.0.0. Fixed in version 3.13.0 to throw `#!cpp std::length_error` instead of emptying the array and - accessing it out of bounds when `idx` equals the maximum value of `size_type`. + accessing it out of bounds when `idx` equals the maximum value of `size_type`. A missing index in the const version + is guarded by a runtime assertion since version 3.13.0. 2. Added in version 1.0.0. Added overloads for `T* key` in version 1.1.0. Removed overloads for `T* key` (replaced by 3) in version 3.11.0. 3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as already supported by [`at`](at.md), [`value`](value.md), [`find`](find.md), and other lookup functions. -4. Added in version 2.0.0. +4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since + version 3.13.0. diff --git a/docs/mkdocs/docs/features/assertions.md b/docs/mkdocs/docs/features/assertions.md index e0b850115..dadb0bd87 100644 --- a/docs/mkdocs/docs/features/assertions.md +++ b/docs/mkdocs/docs/features/assertions.md @@ -16,14 +16,15 @@ before including the `json.hpp` header. ## Function with runtime assertions -### Unchecked object access to a const value +### Unchecked access to a const value -Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for objects. Whereas a missing -key is added in the case of non-const objects, accessing a const object with a missing key is undefined behavior (think -of a dereferenced null pointer) and yields a runtime assertion. +Function [`operator[]`](../api/basic_json/operator%5B%5D.md) implements unchecked access for arrays and objects. Whereas +a missing element is added in the case of non-const values, accessing a const value with a missing object key or an +invalid array index is undefined behavior (think of a dereferenced null pointer) and yields a runtime assertion. This +also applies to a [JSON pointer](json_pointer.md) that refers to a missing key or an invalid index. -If you are not sure whether an element in an object exists, use checked access with the -[`at` function](../api/basic_json/at.md) or call the [`contains` function](../api/basic_json/contains.md) before. +If you are not sure whether an element exists, use checked access with the [`at` function](../api/basic_json/at.md) +or call the [`contains` function](../api/basic_json/contains.md) before. See also the documentation on [element access](element_access/index.md). @@ -46,7 +47,30 @@ See also the documentation on [element access](element_access/index.md). Output: ``` - Assertion failed: (m_value.object->find(key) != m_value.object->end()), function operator[], file json.hpp, line 2144. + Assertion failed: (it != m_data.m_value.object->end()), function operator[], file json.hpp, line 28795. + ``` + +??? example "Example 2: Invalid array index in a JSON pointer" + + The following code will trigger an assertion at runtime: + + ```cpp + #include + + using json = nlohmann::json; + using namespace nlohmann::literals; + + int main() + { + const json j = {{"array", {1, 2, 3}}}; + auto v = j["/array/5"_json_pointer]; + } + ``` + + Output: + + ``` + Assertion failed: (idx < m_data.m_value.array->size()), function operator[], file json.hpp, line 28758. ``` ### Constructing from an uninitialized iterator range diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index cf3720a25..e34acb6d3 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -536,6 +536,10 @@ class json_pointer @return const reference to the JSON value pointed to by the JSON pointer + @pre Every object key and array index the pointer refers to exists. + Like the const operator[] for keys and indices, a missing one is + undefined behavior, guarded by a runtime assertion. + @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @@ -550,7 +554,8 @@ class json_pointer { case detail::value_t::object: { - // use unchecked object access + // use unchecked object access; the const operator[] + // asserts that the key exists ptr = &ptr->operator[](reference_token); break; } @@ -563,7 +568,8 @@ class json_pointer JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); } - // use unchecked array access + // use unchecked array access; the const operator[] + // asserts that the index exists ptr = &ptr->operator[](array_index(reference_token)); break; } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index bb78b211d..1ff12c7f3 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -3065,6 +3065,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { + JSON_ASSERT(idx < m_data.m_value.array->size()); return m_data.m_value.array->operator[](idx); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c4f3313ae..9e27f62f0 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20105,6 +20105,10 @@ class json_pointer @return const reference to the JSON value pointed to by the JSON pointer + @pre Every object key and array index the pointer refers to exists. + Like the const operator[] for keys and indices, a missing one is + undefined behavior, guarded by a runtime assertion. + @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @@ -20119,7 +20123,8 @@ class json_pointer { case detail::value_t::object: { - // use unchecked object access + // use unchecked object access; the const operator[] + // asserts that the key exists ptr = &ptr->operator[](reference_token); break; } @@ -20132,7 +20137,8 @@ class json_pointer JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); } - // use unchecked array access + // use unchecked array access; the const operator[] + // asserts that the index exists ptr = &ptr->operator[](array_index(reference_token)); break; } @@ -29764,6 +29770,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) { + JSON_ASSERT(idx < m_data.m_value.array->size()); return m_data.m_value.array->operator[](idx); } From df27cc3d4cdf9ecb9337524c2af7752967ebf4ca Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:21 +0200 Subject: [PATCH 09/27] Add scalar-on-left overloads for legacy discarded comparisons in C++20 (#5682) With JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 and C++20, a scalar on the left-hand side of <= or >= (e.g., `1 <= discarded`) yielded false instead of the documented true. The C++20 legacy block only had member operators, which are only candidates when the basic_json is the left operand; for a scalar on the left, overload resolution picked the candidate rewritten from operator<=>, which does not emulate the legacy behavior. The C++17 branch already has scalar-on-the-left friend overloads for <= and >=; add the equivalent pair to the C++20 legacy block. Added a regression test to tests/src/unit-comparison.cpp covering all four operand orders for both operators. Fixes #5665. Signed-off-by: Niels Lohmann --- ...n_use_legacy_discarded_value_comparison.md | 3 +++ include/nlohmann/json.hpp | 21 ++++++++++++++++++ single_include/nlohmann/json.hpp | 21 ++++++++++++++++++ tests/src/unit-comparison.cpp | 22 +++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md index b6efd8dbd..e61ce1a3e 100644 --- a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md +++ b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md @@ -81,3 +81,6 @@ When the macro is not defined, the library will define it to its default value. ## Version history - Added in version 3.11.0. +- Fixed in version 3.13.0 so `<=` and `>=` also emulate the legacy behavior in C++20 when the JSON value is the + right-hand operand of a scalar comparison; before, only the 3-way-comparison-rewritten candidate was found, which + yielded `#!cpp false` instead of `#!cpp true`. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 1ff12c7f3..b802b4b59 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4939,6 +4939,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return *this >= basic_json(rhs); } + + // a scalar on the left-hand side would otherwise select the candidate + // rewritten from operator<=>, which does not emulate the legacy behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } #endif #else /// @brief comparison: equal diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9e27f62f0..2adc9978f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -31644,6 +31644,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return *this >= basic_json(rhs); } + + // a scalar on the left-hand side would otherwise select the candidate + // rewritten from operator<=>, which does not emulate the legacy behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } #endif #else /// @brief comparison: equal diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index febfd9b42..a65d5c1b3 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -750,6 +750,28 @@ TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P24 CHECK_FALSE(j != i); } } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON +TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode") +{ + // Issue #5665: with a scalar on the left-hand side, <= and >= only had the + // candidate rewritten from operator<=>, which does not emulate the legacy + // discarded-value behavior. Check that scalar-on-the-left now matches the + // other three operand orders. + const json discarded(json::value_t::discarded); + const json one = 1; + + CHECK(discarded <= 1); + CHECK(discarded >= 1); + CHECK(one <= discarded); + CHECK(one >= discarded); + CHECK(1 <= discarded); + CHECK(1 >= discarded); + CHECK(1.5 <= discarded); + CHECK(1.5 >= discarded); +} +#endif + #endif namespace From 6f2048cd5d609c055742d91539457567a463cb4d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:25 +0200 Subject: [PATCH 10/27] Accept lvalues in ordered_map::emplace's value parameter (#5685) * Accept lvalues in ordered_map::emplace's value parameter ordered_map::emplace(key, value) took the mapped value only by T&&, an rvalue reference rather than a forwarding reference, so ordered_json::emplace("a", value) failed to compile whenever value was an lvalue or a const lvalue, even though the same call compiles for json (whose object_t is std::map, with a variadic emplace). Turn the value parameter into a separately-deduced forwarding reference, constrained with std::is_constructible so the overloads still only accept something convertible to the mapped type. std::map-compatible semantics are unchanged: emplace still does nothing if the key already exists. Open PR #5609 also touches ordered_map.hpp (moving values on vector growth); this change only touches the two emplace() overloads and should not conflict. Fixes #5673. Signed-off-by: Niels Lohmann * Avoid astyle's padding in ordered_map::emplace's template headers Use detail::conjunction instead of && and drop the redundant V&& in detail::is_constructible, so astyle keeps the usual template formatting. Addresses review comment by @gregmarr. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/emplace.md | 2 + include/nlohmann/ordered_map.hpp | 15 +++-- single_include/nlohmann/json.hpp | 15 +++-- tests/src/unit-ordered_json.cpp | 41 ++++++++++++ tests/src/unit-ordered_map.cpp | 73 ++++++++++++++++++++++ 5 files changed, 134 insertions(+), 12 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/emplace.md b/docs/mkdocs/docs/api/basic_json/emplace.md index 26044a597..09953d347 100644 --- a/docs/mkdocs/docs/api/basic_json/emplace.md +++ b/docs/mkdocs/docs/api/basic_json/emplace.md @@ -70,3 +70,5 @@ Logarithmic in the size of the container, O(log(`size()`)). ## Version history - Since version 2.0.8. +- Fixed in version 3.13.0: for [`ordered_json`](../ordered_json.md), the value could previously only be passed as an + rvalue; it can now also be passed as an lvalue or a `#!cpp const` lvalue, matching the behavior of `json`. diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 656f24264..d1c247483 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -74,7 +74,9 @@ template , return *this; } - std::pair emplace(const key_type& key, T&& t) + template::value, int> = 0> + std::pair emplace(const key_type& key, V && t) { for (auto it = this->begin(); it != this->end(); ++it) { @@ -83,13 +85,14 @@ template , return {it, false}; } } - append(key, std::forward(t)); + append(key, std::forward(t)); return {std::prev(this->end()), true}; } - template::value, int> = 0> - std::pair emplace(KeyType && key, T && t) + template, + detail::is_constructible>::value, int> = 0> + std::pair emplace(KeyType && key, V && t) { for (auto it = this->begin(); it != this->end(); ++it) { @@ -98,7 +101,7 @@ template , return {it, false}; } } - append(std::forward(key), std::forward(t)); + append(std::forward(key), std::forward(t)); return {std::prev(this->end()), true}; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2adc9978f..fe3547ab2 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -26401,7 +26401,9 @@ template , return *this; } - std::pair emplace(const key_type& key, T&& t) + template::value, int> = 0> + std::pair emplace(const key_type& key, V && t) { for (auto it = this->begin(); it != this->end(); ++it) { @@ -26410,13 +26412,14 @@ template , return {it, false}; } } - append(key, std::forward(t)); + append(key, std::forward(t)); return {std::prev(this->end()), true}; } - template::value, int> = 0> - std::pair emplace(KeyType && key, T && t) + template, + detail::is_constructible>::value, int> = 0> + std::pair emplace(KeyType && key, V && t) { for (auto it = this->begin(); it != this->end(); ++it) { @@ -26425,7 +26428,7 @@ template , return {it, false}; } } - append(std::forward(key), std::forward(t)); + append(std::forward(key), std::forward(t)); return {std::prev(this->end()), true}; } diff --git a/tests/src/unit-ordered_json.cpp b/tests/src/unit-ordered_json.cpp index 45fbf5493..135dedade 100644 --- a/tests/src/unit-ordered_json.cpp +++ b/tests/src/unit-ordered_json.cpp @@ -196,3 +196,44 @@ TEST_CASE("regression test - diff() must account for ordered_json member order") CHECK(a.patch(p) == b); } } + +TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rvalue value") +{ + SECTION("lvalue value") + { + ordered_json oj = ordered_json::object(); + ordered_json value = 1; + auto res = oj.emplace("a", value); + CHECK(res.second == true); + CHECK(oj.dump() == "{\"a\":1}"); + } + + SECTION("const lvalue value") + { + ordered_json oj = ordered_json::object(); + const ordered_json value = 1; + auto res = oj.emplace("a", value); + CHECK(res.second == true); + CHECK(oj.dump() == "{\"a\":1}"); + } + + SECTION("rvalue value") + { + ordered_json oj = ordered_json::object(); + auto res = oj.emplace("a", ordered_json(1)); + CHECK(res.second == true); + CHECK(oj.dump() == "{\"a\":1}"); + } + + SECTION("existing key is not overwritten (std::map-compatible semantics)") + { + ordered_json oj = ordered_json::object(); + ordered_json value = 1; + oj.emplace("a", value); + + ordered_json other_value = 2; + auto res = oj.emplace("a", other_value); + CHECK(res.second == false); + CHECK(oj.dump() == "{\"a\":1}"); + } +} diff --git a/tests/src/unit-ordered_map.cpp b/tests/src/unit-ordered_map.cpp index 98b6fa0d1..dce3f61a5 100644 --- a/tests/src/unit-ordered_map.cpp +++ b/tests/src/unit-ordered_map.cpp @@ -403,6 +403,79 @@ TEST_CASE("ordered_map") CHECK(om.size() == 4); } } + + SECTION("emplace") + { + // regression test for issue #5673: the mapped-value parameter must + // accept lvalues and const lvalues, not just rvalues + ordered_map om; + om["eins"] = "one"; + om["zwei"] = "two"; + om["drei"] = "three"; + + SECTION("with T&& (rvalue)") + { + auto res1 = om.emplace("eins", std::string("1")); + CHECK(res1.first == om.begin()); + CHECK(res1.second == false); + CHECK(om.size() == 3); + CHECK(om.at("eins") == "one"); // existing key is not overwritten + + auto res4 = om.emplace("vier", std::string("four")); + CHECK(res4.first == om.begin() + 3); + CHECK(res4.second == true); + CHECK(om.size() == 4); + CHECK(om.at("vier") == "four"); + } + + SECTION("with T& (lvalue)") + { + std::string one = "1"; + std::string four = "four"; + + auto res1 = om.emplace("eins", one); + CHECK(res1.first == om.begin()); + CHECK(res1.second == false); + CHECK(om.size() == 3); + CHECK(om.at("eins") == "one"); // existing key is not overwritten + + auto res4 = om.emplace("vier", four); + CHECK(res4.first == om.begin() + 3); + CHECK(res4.second == true); + CHECK(om.size() == 4); + CHECK(om.at("vier") == "four"); + CHECK(four == "four"); // source was copied, not moved from + } + + SECTION("with const T&") + { + const std::string one = "1"; + const std::string four = "four"; + + auto res1 = om.emplace("eins", one); + CHECK(res1.first == om.begin()); + CHECK(res1.second == false); + CHECK(om.size() == 3); + + auto res4 = om.emplace("vier", four); + CHECK(res4.first == om.begin() + 3); + CHECK(res4.second == true); + CHECK(om.size() == 4); + CHECK(om.at("vier") == "four"); + } + + SECTION("with key of key_type (non-template overload)") + { + const std::string key_vier{"vier"}; + std::string four = "four"; + + auto res4 = om.emplace(key_vier, four); + CHECK(res4.first == om.begin() + 3); + CHECK(res4.second == true); + CHECK(om.size() == 4); + CHECK(om.at("vier") == "four"); + } + } } TEST_CASE("ordered_map growth") From 0d01d6ae9074e167bcdca0bc6e592c02e900c692 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:29 +0200 Subject: [PATCH 11/27] Classify leaves with operator<=> itself past the nesting bound (#5686) * Classify leaves with operator<=> itself past the nesting bound In C++20, an ordered comparison past the nesting bound classified a pair of leaves by asking == first and then order_leaves(), which calls < and > - both derived from <=>. For a pair of binary values with the same bytes but a different subtype, == reports them unequal, while <=> (through std::vector::operator<=>) reports them equivalent, so the pair ended the comparison as unordered instead of letting the next element decide - unlike an array or object within the bound, which compares such a pair with its own operator<=> and gets equivalent. So operator<=>, and the <, <=, >, >= derived from it, could give a different result for the same two values depending on how deeply the values were nested, or unordered at every depth with JSON_NO_THREAD_LOCAL defined. compare_leaves() now classifies such a pair in C++20 with operator<=> itself instead, matching how a value within the bound is compared; the equality-only and pre-C++20 ordered cases are unchanged. Which of the three runs is chosen by overloading on std::integral_constant, the same tag dispatch order_leaves() already uses, rather than a runtime "if (Ordered)" on a template parameter, which MSVC would flag as a constant condition (C4127). Added a regression test to unit-comparison.cpp that nests such a pair 0, 127, 128 and 200 levels deep (127 stays within the 128-level bound, 128 and 200 do not) and checks that operator<=> and operator< agree at every depth. Fixes #5654. Signed-off-by: Niels Lohmann * Drop the version history note for a bug that was never released The regression came from #5390, which is not in any release. Addresses review comment by @gregmarr. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- include/nlohmann/json.hpp | 52 +++++++++++++++++++++++++++++++- single_include/nlohmann/json.hpp | 52 +++++++++++++++++++++++++++++++- tests/src/unit-comparison.cpp | 48 +++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index b802b4b59..bf3052748 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1528,15 +1528,65 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec */ template static compare_result compare_leaves(const_reference lhs, const_reference rhs) noexcept + { + return compare_leaves(lhs, rhs, std::integral_constant {}); + } + + /// @brief compare two leaves that are only being checked for equality + static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::false_type /*ordered*/) noexcept { if (lhs == rhs) { return compare_result::equal; } - return order_leaves(lhs, rhs, std::integral_constant {}); + return order_leaves(lhs, rhs, std::false_type {}); } +#if JSON_HAS_THREE_WAY_COMPARISON + /*! + @brief compare two leaves that are being ordered, for operator<=> + + Reached only from operator<=>, so the leaves must be classified exactly + as operator<=> classifies them - which is not the same as asking + == and then order_leaves(), the way the other overload does it. The two + disagree on a binary value: == also compares the subtype, but <=> compares + only the bytes, through std::vector::operator<=>. Using <=> + itself here keeps a leaf pair classified the same way regardless of how + deep it is nested - == first would again call operator<=> a level down + through order_leaves(), but call it after a mismatching == already ended + the comparison for a pair that <=> alone would still call equivalent. + */ + static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept + { + const std::partial_ordering order = lhs <=> rhs; // *NOPAD* + if (order == 0) + { + return compare_result::equal; + } + if (order < 0) + { + return compare_result::less; + } + if (order > 0) + { + return compare_result::greater; + } + return compare_result::unordered; + } +#else + /// @brief compare two leaves that are being ordered, for operator< + static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept + { + if (lhs == rhs) + { + return compare_result::equal; + } + + return order_leaves(lhs, rhs, std::true_type {}); + } +#endif + /*! @brief compare two object keys diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index fe3547ab2..3c1048003 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -28236,15 +28236,65 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec */ template static compare_result compare_leaves(const_reference lhs, const_reference rhs) noexcept + { + return compare_leaves(lhs, rhs, std::integral_constant {}); + } + + /// @brief compare two leaves that are only being checked for equality + static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::false_type /*ordered*/) noexcept { if (lhs == rhs) { return compare_result::equal; } - return order_leaves(lhs, rhs, std::integral_constant {}); + return order_leaves(lhs, rhs, std::false_type {}); } +#if JSON_HAS_THREE_WAY_COMPARISON + /*! + @brief compare two leaves that are being ordered, for operator<=> + + Reached only from operator<=>, so the leaves must be classified exactly + as operator<=> classifies them - which is not the same as asking + == and then order_leaves(), the way the other overload does it. The two + disagree on a binary value: == also compares the subtype, but <=> compares + only the bytes, through std::vector::operator<=>. Using <=> + itself here keeps a leaf pair classified the same way regardless of how + deep it is nested - == first would again call operator<=> a level down + through order_leaves(), but call it after a mismatching == already ended + the comparison for a pair that <=> alone would still call equivalent. + */ + static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept + { + const std::partial_ordering order = lhs <=> rhs; // *NOPAD* + if (order == 0) + { + return compare_result::equal; + } + if (order < 0) + { + return compare_result::less; + } + if (order > 0) + { + return compare_result::greater; + } + return compare_result::unordered; + } +#else + /// @brief compare two leaves that are being ordered, for operator< + static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept + { + if (lhs == rhs) + { + return compare_result::equal; + } + + return order_leaves(lhs, rhs, std::true_type {}); + } +#endif + /*! @brief compare two object keys diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index a65d5c1b3..4b66f1d07 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -1020,3 +1020,51 @@ TEST_CASE("containers are compared element by element") } } } + +#if JSON_HAS_THREE_WAY_COMPARISON +// JSON_HAS_CPP_20 (do not remove; see note at top of file) +TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth") +{ + // #5654: std::vector::operator<=>, which the binary type's + // own operator<=> uses, ignores the subtype that operator== checks. So a + // pair of binary values with the same bytes but a different subtype is + // unequal, yet <=>-equivalent - the same inconsistency between == and <=> + // that a NaN has. Within the nesting bound, an array compares itself + // with std::vector's own operator<=>, which treats an equivalent pair as + // undecided and lets the next element decide, same as + // std::lexicographical_compare_three_way does. Past the bound, + // compare_iteratively() takes over and must classify the pair the + // same way, or the result of operator<=> - and of <, which C++20 derives + // from it - depends on how deeply the values are nested. + const json a = json::array({json::binary({1}, 1), 1}); + const json b = json::array({json::binary({1}, 2), 2}); + + // the root inconsistency: unequal, yet <=>-equivalent + CHECK_FALSE(a[0] == b[0]); + CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD* + + const auto deep = [](const json & j, const std::size_t depth) + { + json result = j; + for (std::size_t i = 0; i < depth; ++i) + { + result = json::array({std::move(result)}); + } + return result; + }; + + // 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not, + // and must still agree with the levels that do + for (const std::size_t depth : std::vector {0, 127, 128, 200}) + { + CAPTURE(depth); + const json x = deep(a, depth); + const json y = deep(b, depth); + CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD* + CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD* + CHECK(x < y); + CHECK(y > x); + CHECK_FALSE(y < x); + } +} +#endif From b730946432dd9b22ecd012515f50a191d96954fc Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:32 +0200 Subject: [PATCH 12/27] Fix key types convertible to std::string_view breaking lookups (#5689) Since #4958, a key type implicitly convertible to std::string_view was accepted by is_usable_as_basic_json_key_type without checking that the object's comparator can actually compare object_t::key_type with that key type. The key was then forwarded unchanged to the underlying map, so const operator[], at, find, count, contains, erase and value failed to compile (a hard error inside ) for a key convertible only to std::string_view, and value() rejected such keys outright. For keys convertible to both std::string and std::string_view, the KeyType&& templates now won overload resolution over the object_t::key_type overloads and then failed the same way, a regression from 3.12.0. Only the non-const operator[] worked, because it uses emplace(), which constructs a std::string from the key explicitly. ordered_json was not affected, since ordered_map checks comparability itself. Add a trait, is_string_view_convertible_key_type, that recognizes a key type that is convertible to std::string_view but not directly comparable with the object's key type, provided std::string_view itself is comparable with it. at(), operator[], find(), count(), contains(), erase() and value() now route such keys through a new lookup_key() helper that converts them to std::string_view before they reach the object, matching how the object's transparent comparator already supports std::string_view lookups. Fixes #5663. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/at.md | 4 +- docs/mkdocs/docs/api/basic_json/contains.md | 4 +- docs/mkdocs/docs/api/basic_json/count.md | 4 +- docs/mkdocs/docs/api/basic_json/erase.md | 4 +- docs/mkdocs/docs/api/basic_json/find.md | 4 +- docs/mkdocs/docs/api/basic_json/value.md | 4 +- include/nlohmann/detail/meta/type_traits.hpp | 28 ++++- include/nlohmann/json.hpp | 44 ++++++-- single_include/nlohmann/json.hpp | 72 +++++++++--- tests/src/unit-element_access2.cpp | 111 +++++++++++++++++++ 10 files changed, 243 insertions(+), 36 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/at.md b/docs/mkdocs/docs/api/basic_json/at.md index 60daf38e3..18f108964 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -238,5 +238,7 @@ Strong exception safety: if an exception occurs, the original value stays intact 1. Added in version 1.0.0. 2. Added in version 1.0.0. -3. Added in version 3.11.0. +3. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as + already supported by [`operator[]`](operator[].md), [`value`](value.md), [`find`](find.md), and other lookup + functions. 4. Added in version 2.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index 73bcf6f0c..63ec87a1d 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -131,7 +131,9 @@ Logarithmic in the size of the JSON object. ## Version history 1. Added in version 3.11.0. -2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0. +2. Added in version 3.6.0. Extended template `KeyType` to support comparable types in version 3.11.0. Fixed in + version 3.13.0 to consistently accept `std::string_view`-convertible keys, as already supported by + [`operator[]`](operator[].md), [`at`](at.md), [`value`](value.md), and other lookup functions. 3. Added in version 3.7.0. 4. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of causing undefined behavior at runtime. diff --git a/docs/mkdocs/docs/api/basic_json/count.md b/docs/mkdocs/docs/api/basic_json/count.md index bffc46534..14b707525 100644 --- a/docs/mkdocs/docs/api/basic_json/count.md +++ b/docs/mkdocs/docs/api/basic_json/count.md @@ -84,6 +84,8 @@ Logarithmic in the size of the JSON object. ## Version history 1. Added in version 3.11.0. -2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0. +2. Added in version 1.0.0. Changed parameter `key` type to `KeyType&&` in version 3.11.0. Fixed in version 3.13.0 to + consistently accept `std::string_view`-convertible keys, as already supported by [`operator[]`](operator[].md), + [`at`](at.md), [`value`](value.md), and other lookup functions. 3. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time instead of causing undefined behavior at runtime. diff --git a/docs/mkdocs/docs/api/basic_json/erase.md b/docs/mkdocs/docs/api/basic_json/erase.md index d1e6d6d22..47531fed8 100644 --- a/docs/mkdocs/docs/api/basic_json/erase.md +++ b/docs/mkdocs/docs/api/basic_json/erase.md @@ -213,5 +213,7 @@ Strong exception safety: if an exception occurs, the original value stays intact 1. Added in version 1.0.0. Added support for binary types in version 3.8.0. 2. Added in version 1.0.0. Added support for binary types in version 3.8.0. 3. Added in version 1.0.0. -4. Added in version 3.11.0. +4. Added in version 3.11.0. Fixed in version 3.13.0 to consistently accept `std::string_view`-convertible keys, as + already supported by [`operator[]`](operator[].md), [`at`](at.md), [`value`](value.md), and other lookup + functions. 5. Added in version 1.0.0. diff --git a/docs/mkdocs/docs/api/basic_json/find.md b/docs/mkdocs/docs/api/basic_json/find.md index bc746ee2f..59c2eab68 100644 --- a/docs/mkdocs/docs/api/basic_json/find.md +++ b/docs/mkdocs/docs/api/basic_json/find.md @@ -88,6 +88,8 @@ Logarithmic in the size of the JSON object. ## Version history 1. Added in version 3.11.0. -2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0. +2. Added in version 1.0.0. Changed to support comparable types in version 3.11.0. Fixed in version 3.13.0 to + consistently accept `std::string_view`-convertible keys, as already supported by [`operator[]`](operator[].md), + [`at`](at.md), [`value`](value.md), and other lookup functions. 3. Deleted overloads for integral key types added in version 3.13.0 to reject such calls at compile time instead of causing undefined behavior at runtime. diff --git a/docs/mkdocs/docs/api/basic_json/value.md b/docs/mkdocs/docs/api/basic_json/value.md index 56f4bcc0f..80f5691dc 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -222,7 +222,9 @@ changes to any JSON value. 1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version 3.11.0. Deleted overload for integral key types added in version 3.13.0 to reject such calls at compile time instead of causing undefined behavior at runtime. -2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2. +2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2. Fixed in version 3.13.0 + to consistently accept `std::string_view`-convertible keys, as already supported by + [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and other lookup functions. 3. Added in version 2.0.2. Extended to work with arrays in version 3.13.0, including fixing an issue where resolving `ptr` through an array unexpectedly threw `out_of_range` instead of returning the resolved element (or `default_value`, as documented). diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 96b70a774..2f837e046 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -760,6 +760,30 @@ using is_usable_as_key_type = typename std::conditional < std::true_type, std::false_type >::type; +#ifdef JSON_HAS_CPP_17 +// type trait to check if KeyType can only be used as an object key after +// converting it to std::string_view: it is convertible to std::string_view, the +// object's comparator cannot compare it with object_t::key_type directly, but +// can compare a std::string_view. JSON pointers and JSON iterators are ruled out +// first, so that the conversion checks are never instantiated for them (a JSON +// pointer's deprecated conversion to string_t would be named otherwise). +template < typename BasicJsonType, typename KeyTypeCVRef, typename KeyType = uncvref_t, + bool = is_json_pointer::value || is_json_iterator_of::value > +struct is_string_view_convertible_key_type : std::false_type {}; + +template +struct is_string_view_convertible_key_type + : std::integral_constant < bool, + std::is_convertible::value + && !is_usable_as_key_type::value + && is_usable_as_key_type::value > {}; +#else +template +struct is_string_view_convertible_key_type : std::false_type {}; +#endif + // type trait to check if KeyType can be used as an object key // true if: // - KeyType is comparable with BasicJsonType::object_t::key_type @@ -773,9 +797,7 @@ using is_usable_as_basic_json_key_type = typename std::conditional < typename BasicJsonType::object_t::key_type, KeyTypeCVRef, RequireTransparentComparator, ExcludeObjectKeyType>::value && !is_json_iterator_of::value) -#ifdef JSON_HAS_CPP_17 - || std::is_convertible::value -#endif + || is_string_view_convertible_key_type::value , std::true_type, std::false_type >::type; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index bf3052748..f83c29480 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -813,6 +813,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } + /// @brief the key to look up an object member with: the key itself, or its + /// std::string_view if the object can only be searched with that + template < typename KeyType, detail::enable_if_t < + !detail::is_string_view_convertible_key_type::value, int > = 0 > + static KeyType && lookup_key(KeyType && key) noexcept + { + return std::forward(key); + } + +#ifdef JSON_HAS_CPP_17 + template < typename KeyType, detail::enable_if_t < + detail::is_string_view_convertible_key_type::value, int > = 0 > + static std::string_view lookup_key(KeyType && key) + { + return std::forward(key); + } +#endif + /// @brief erase an element from the object and return the following one /// Not every map returns an iterator from erase(iterator): some containers /// (e.g., Abseil's hash maps) return void to avoid computing a successor @@ -3008,7 +3026,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); } - auto it = m_data.m_value.object->find(std::forward(key)); + auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); if (it == m_data.m_value.object->end()) { JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); @@ -3046,7 +3064,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); } - auto it = m_data.m_value.object->find(std::forward(key)); + auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); if (it == m_data.m_value.object->end()) { JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); @@ -3190,7 +3208,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto result = m_data.m_value.object->emplace(std::forward(key), nullptr); + auto result = m_data.m_value.object->emplace(lookup_key(std::forward(key)), nullptr); return set_parent(result.first->second); } @@ -3206,7 +3224,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto it = m_data.m_value.object->find(std::forward(key)); + auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); JSON_ASSERT(it != m_data.m_value.object->end()); return it->second; } @@ -3216,8 +3234,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec private: template - using is_comparable_with_object_key = detail::is_comparable < - object_comparator_t, const typename object_t::key_type&, KeyType >; + using is_comparable_with_object_key = std::integral_constant < bool, + detail::is_comparable < + object_comparator_t, const typename object_t::key_type&, KeyType >::value + || detail::is_string_view_convertible_key_type::value >; template using value_return_type = std::conditional < @@ -3604,7 +3624,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); } - const auto it = m_data.m_value.object->find(std::forward(key)); + const auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); if (it != m_data.m_value.object->end()) { m_data.m_value.object->erase(it); @@ -3631,7 +3651,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_usable_as_basic_json_key_type::value, int> = 0> size_type erase(KeyType && key) { - return erase_internal(std::forward(key)); + return erase_internal(lookup_key(std::forward(key))); } /// @brief remove element from a JSON array given an index @@ -3711,7 +3731,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(std::forward(key)); + result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); } return result; @@ -3727,7 +3747,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(std::forward(key)); + result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); } return result; @@ -3750,7 +3770,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec size_type count(KeyType && key) const { // return 0 for all nonobject types - return is_object() ? m_data.m_value.object->count(std::forward(key)) : 0; + return is_object() ? m_data.m_value.object->count(lookup_key(std::forward(key))) : 0; } /// @brief check the existence of an element in a JSON object @@ -3768,7 +3788,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT bool contains(KeyType && key) const { - return is_object() && m_data.m_value.object->find(std::forward(key)) != m_data.m_value.object->end(); + return is_object() && m_data.m_value.object->find(lookup_key(std::forward(key))) != m_data.m_value.object->end(); } /// @brief check the existence of an element in a JSON object given a JSON pointer diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3c1048003..689e233bd 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4760,6 +4760,30 @@ using is_usable_as_key_type = typename std::conditional < std::true_type, std::false_type >::type; +#ifdef JSON_HAS_CPP_17 +// type trait to check if KeyType can only be used as an object key after +// converting it to std::string_view: it is convertible to std::string_view, the +// object's comparator cannot compare it with object_t::key_type directly, but +// can compare a std::string_view. JSON pointers and JSON iterators are ruled out +// first, so that the conversion checks are never instantiated for them (a JSON +// pointer's deprecated conversion to string_t would be named otherwise). +template < typename BasicJsonType, typename KeyTypeCVRef, typename KeyType = uncvref_t, + bool = is_json_pointer::value || is_json_iterator_of::value > +struct is_string_view_convertible_key_type : std::false_type {}; + +template +struct is_string_view_convertible_key_type + : std::integral_constant < bool, + std::is_convertible::value + && !is_usable_as_key_type::value + && is_usable_as_key_type::value > {}; +#else +template +struct is_string_view_convertible_key_type : std::false_type {}; +#endif + // type trait to check if KeyType can be used as an object key // true if: // - KeyType is comparable with BasicJsonType::object_t::key_type @@ -4773,9 +4797,7 @@ using is_usable_as_basic_json_key_type = typename std::conditional < typename BasicJsonType::object_t::key_type, KeyTypeCVRef, RequireTransparentComparator, ExcludeObjectKeyType>::value && !is_json_iterator_of::value) -#ifdef JSON_HAS_CPP_17 - || std::is_convertible::value -#endif + || is_string_view_convertible_key_type::value , std::true_type, std::false_type >::type; @@ -27521,6 +27543,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } + /// @brief the key to look up an object member with: the key itself, or its + /// std::string_view if the object can only be searched with that + template < typename KeyType, detail::enable_if_t < + !detail::is_string_view_convertible_key_type::value, int > = 0 > + static KeyType && lookup_key(KeyType && key) noexcept + { + return std::forward(key); + } + +#ifdef JSON_HAS_CPP_17 + template < typename KeyType, detail::enable_if_t < + detail::is_string_view_convertible_key_type::value, int > = 0 > + static std::string_view lookup_key(KeyType && key) + { + return std::forward(key); + } +#endif + /// @brief erase an element from the object and return the following one /// Not every map returns an iterator from erase(iterator): some containers /// (e.g., Abseil's hash maps) return void to avoid computing a successor @@ -29716,7 +29756,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); } - auto it = m_data.m_value.object->find(std::forward(key)); + auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); if (it == m_data.m_value.object->end()) { JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); @@ -29754,7 +29794,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); } - auto it = m_data.m_value.object->find(std::forward(key)); + auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); if (it == m_data.m_value.object->end()) { JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); @@ -29898,7 +29938,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto result = m_data.m_value.object->emplace(std::forward(key), nullptr); + auto result = m_data.m_value.object->emplace(lookup_key(std::forward(key)), nullptr); return set_parent(result.first->second); } @@ -29914,7 +29954,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto it = m_data.m_value.object->find(std::forward(key)); + auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); JSON_ASSERT(it != m_data.m_value.object->end()); return it->second; } @@ -29924,8 +29964,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec private: template - using is_comparable_with_object_key = detail::is_comparable < - object_comparator_t, const typename object_t::key_type&, KeyType >; + using is_comparable_with_object_key = std::integral_constant < bool, + detail::is_comparable < + object_comparator_t, const typename object_t::key_type&, KeyType >::value + || detail::is_string_view_convertible_key_type::value >; template using value_return_type = std::conditional < @@ -30312,7 +30354,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); } - const auto it = m_data.m_value.object->find(std::forward(key)); + const auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); if (it != m_data.m_value.object->end()) { m_data.m_value.object->erase(it); @@ -30339,7 +30381,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_usable_as_basic_json_key_type::value, int> = 0> size_type erase(KeyType && key) { - return erase_internal(std::forward(key)); + return erase_internal(lookup_key(std::forward(key))); } /// @brief remove element from a JSON array given an index @@ -30419,7 +30461,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(std::forward(key)); + result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); } return result; @@ -30435,7 +30477,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(std::forward(key)); + result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); } return result; @@ -30458,7 +30500,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec size_type count(KeyType && key) const { // return 0 for all nonobject types - return is_object() ? m_data.m_value.object->count(std::forward(key)) : 0; + return is_object() ? m_data.m_value.object->count(lookup_key(std::forward(key))) : 0; } /// @brief check the existence of an element in a JSON object @@ -30476,7 +30518,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT bool contains(KeyType && key) const { - return is_object() && m_data.m_value.object->find(std::forward(key)) != m_data.m_value.object->end(); + return is_object() && m_data.m_value.object->find(lookup_key(std::forward(key))) != m_data.m_value.object->end(); } /// @brief check the existence of an element in a JSON object given a JSON pointer diff --git a/tests/src/unit-element_access2.cpp b/tests/src/unit-element_access2.cpp index 04974c251..a64caff89 100644 --- a/tests/src/unit-element_access2.cpp +++ b/tests/src/unit-element_access2.cpp @@ -1973,4 +1973,115 @@ TEST_CASE("operator[] with user-defined std::string_view-convertible types") } } } + +TEST_CASE("keys convertible to std::string_view work with all lookup functions (regression test for #5663)") +{ + // a key type convertible only to std::string_view: the case #4958 added + // support for, but only the non-const operator[] compiled with it + struct ViewKey + { + operator std::string_view() const + { + return "a"; + } + }; + + // a key type convertible to both std::string and std::string_view: with + // 3.12.0, such a key worked with at, the const operator[], find, count and + // contains via the conversion to std::string; #4958 made the KeyType&& + // templates win overload resolution for it instead, and those then failed + struct DualKey + { + operator std::string() const + { + return "a"; + } + operator std::string_view() const + { + return "a"; + } + }; + + SECTION("nlohmann::json") + { + using json = nlohmann::json; + + SECTION("ViewKey") + { + json j = {{"a", 1}}; + const json& cj = j; + + CHECK(j[ViewKey{}] == 1); + CHECK(cj[ViewKey{}] == 1); + CHECK(j.at(ViewKey{}) == 1); + CHECK(cj.at(ViewKey{}) == 1); + CHECK(j.find(ViewKey{}) != j.end()); + CHECK(cj.find(ViewKey{}) != cj.end()); + CHECK(j.count(ViewKey{}) == 1); + CHECK(j.contains(ViewKey{})); + CHECK(j.value(ViewKey{}, 0) == 1); + CHECK(j.erase(ViewKey{}) == 1); + CHECK(!j.contains("a")); + } + + SECTION("DualKey") + { + json j = {{"a", 1}}; + const json& cj = j; + + CHECK(j[DualKey{}] == 1); + CHECK(cj[DualKey{}] == 1); + CHECK(j.at(DualKey{}) == 1); + CHECK(cj.at(DualKey{}) == 1); + CHECK(j.find(DualKey{}) != j.end()); + CHECK(cj.find(DualKey{}) != cj.end()); + CHECK(j.count(DualKey{}) == 1); + CHECK(j.contains(DualKey{})); + CHECK(j.value(DualKey{}, 0) == 1); + CHECK(j.erase(DualKey{}) == 1); + CHECK(!j.contains("a")); + } + } + + SECTION("nlohmann::ordered_json") + { + using ordered_json = nlohmann::ordered_json; + + SECTION("ViewKey") + { + ordered_json j = {{"a", 1}}; + const ordered_json& cj = j; + + CHECK(j[ViewKey{}] == 1); + CHECK(cj[ViewKey{}] == 1); + CHECK(j.at(ViewKey{}) == 1); + CHECK(cj.at(ViewKey{}) == 1); + CHECK(j.find(ViewKey{}) != j.end()); + CHECK(cj.find(ViewKey{}) != cj.end()); + CHECK(j.count(ViewKey{}) == 1); + CHECK(j.contains(ViewKey{})); + CHECK(j.value(ViewKey{}, 0) == 1); + CHECK(j.erase(ViewKey{}) == 1); + CHECK(!j.contains("a")); + } + + SECTION("DualKey") + { + ordered_json j = {{"a", 1}}; + const ordered_json& cj = j; + + CHECK(j[DualKey{}] == 1); + CHECK(cj[DualKey{}] == 1); + CHECK(j.at(DualKey{}) == 1); + CHECK(cj.at(DualKey{}) == 1); + CHECK(j.find(DualKey{}) != j.end()); + CHECK(cj.find(DualKey{}) != cj.end()); + CHECK(j.count(DualKey{}) == 1); + CHECK(j.contains(DualKey{})); + CHECK(j.value(DualKey{}, 0) == 1); + CHECK(j.erase(DualKey{}) == 1); + CHECK(!j.contains("a")); + } + } +} #endif From 49cd427196332935eb623fa448150425239d2c06 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:36 +0200 Subject: [PATCH 13/27] Copy-construct the base class of a deep copy's elements, not assign it (#5690) The bounded-descent copy added by #5389 built the elements of a deep copy (nested past the 128-level bound) by default-constructing them and then having copy_metadata() assign their base class afterwards. That assignment is only instantiated for values nested past the bound, but being called from copy_structured() at all meant it was compiled for every copy, so a CustomBaseClass that is copy-constructible but not move-assignable (for example one with a const data member) no longer let its basic_json be copy-constructed, at any depth. copy_array_level() and copy_object_level() now build each element with a private-tag-selected constructor that copy-constructs the base class (and, under JSON_DIAGNOSTIC_POSITIONS, copies the positions) directly, the same way the copy constructor already builds elements within the 128-level bound. Copying a basic_json is therefore back to requiring only a copy-constructible base class, as documented and as it was before #5389; copy assignment is unchanged and still requires an assignable one. Fixes #5674. Signed-off-by: Niels Lohmann --- include/nlohmann/json.hpp | 65 ++++++++++++++++++-------- single_include/nlohmann/json.hpp | 65 ++++++++++++++++++-------- tests/src/unit-custom-base-class.cpp | 70 ++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 38 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index f83c29480..268c28c42 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1028,19 +1028,39 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using copy_scratch_value_t = std::pair; using copy_scratch_t = std::vector>; - /// @brief copy everything of @a src into @a dst but its type and value - static void copy_metadata(const basic_json& src, basic_json& dst) - { - // a custom base class is only required to be copy-constructible and - // move-assignable, so the copy has to go through a temporary - static_cast(dst) = json_base_class_t(static_cast(src)); + /// @brief tag selecting the constructor below; used only to build the + /// elements of a deep copy (@ref copy_array_level, @ref copy_object_level) + struct copy_construct_tag {}; + public: + /*! + @brief construct a null value whose base class - and, with @ref + JSON_DIAGNOSTIC_POSITIONS, positions - are copied from @a src + + Copy-constructing @ref json_base_class_t here, rather than default- + constructing the element and assigning its base class afterwards, means + that copying a @ref basic_json only ever requires a copy-constructible + base class, and never a move-assignable one as well. + + @note this constructor has to be public: @ref copy_array_level and + @ref copy_object_level reach it through @ref array_t's or @ref + object_t's own emplace_back(), which constructs the element from + outside @ref basic_json and so cannot call a private constructor. + @ref copy_construct_tag is private, though, and nothing in the + public interface hands out a value of it, so outside code can still + never name it to call this constructor itself. + */ + basic_json(copy_construct_tag /*unused*/, const basic_json& src) + : json_base_class_t(src) #if JSON_DIAGNOSTIC_POSITIONS - dst.start_position = src.start_position; - dst.end_position = src.end_position; + , start_position(src.start_position) + , end_position(src.end_position) #endif + { } + private: + /*! @brief copy the value of @a src into @a dst, which must not be structured @@ -1103,8 +1123,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /*! - @brief copy everything of @a src into the null value @a dst but the children + @brief finish the copy @a dst of @a src that a @ref copy_construct_tag + constructor started, other than the children of an object or array + @a dst already has @a src's base class and, with @ref + JSON_DIAGNOSTIC_POSITIONS, positions; only its value is still missing. Objects and arrays are not copied here; they are appended to @a worklist to be created later by @ref copy_iteratively. Until that happens, @a dst remains a null value, so that a partially built copy can be destroyed at any point @@ -1112,8 +1135,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec */ static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist) { - copy_metadata(src, dst); - if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array) { // defer: dst stays a null value until its container exists @@ -1134,15 +1155,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { const array_t& src_array = *src.m_data.m_value.array; - // create all elements up front: growing the array afterwards could - // invalidate the pointers that are handed to the worklist; resize() - // rather than the fill constructor, because not every array type - // provides the latter (e.g., ones without a matching allocator-aware - // fill constructor) dst.m_data.m_value.array = create(); // only now that the array exists may dst stop being a null value dst.m_data.m_type = value_t::array; - dst.m_data.m_value.array->resize(src_array.size()); + + // create every element - its base class already copy-constructed from + // its counterpart in src, via the copy_construct_tag constructor - + // before any of their addresses are handed to worklist below: growing + // the array while that is going on could reallocate it and invalidate + // addresses taken from an earlier iteration + for (const auto& src_element : src_array) + { + dst.m_data.m_value.array->emplace_back(copy_construct_tag{}, src_element); + } auto dst_it = dst.m_data.m_value.array->begin(); for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it) @@ -1160,12 +1185,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // build the complete key skeleton and hand it to the object's range // constructor: adding the keys one by one would be quadratic for object - // types that are backed by a vector, such as nlohmann::ordered_map + // types that are backed by a vector, such as nlohmann::ordered_map; each + // value's base class is already copy-constructed from its counterpart + // in src, via the copy_construct_tag constructor scratch.clear(); scratch.reserve(src_object.size()); for (const auto& element : src_object) { - scratch.emplace_back(element.first, basic_json()); + scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second)); } dst.m_data.m_value.object = create(std::make_move_iterator(scratch.begin()), diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 689e233bd..349977636 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -27758,19 +27758,39 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using copy_scratch_value_t = std::pair; using copy_scratch_t = std::vector>; - /// @brief copy everything of @a src into @a dst but its type and value - static void copy_metadata(const basic_json& src, basic_json& dst) - { - // a custom base class is only required to be copy-constructible and - // move-assignable, so the copy has to go through a temporary - static_cast(dst) = json_base_class_t(static_cast(src)); + /// @brief tag selecting the constructor below; used only to build the + /// elements of a deep copy (@ref copy_array_level, @ref copy_object_level) + struct copy_construct_tag {}; + public: + /*! + @brief construct a null value whose base class - and, with @ref + JSON_DIAGNOSTIC_POSITIONS, positions - are copied from @a src + + Copy-constructing @ref json_base_class_t here, rather than default- + constructing the element and assigning its base class afterwards, means + that copying a @ref basic_json only ever requires a copy-constructible + base class, and never a move-assignable one as well. + + @note this constructor has to be public: @ref copy_array_level and + @ref copy_object_level reach it through @ref array_t's or @ref + object_t's own emplace_back(), which constructs the element from + outside @ref basic_json and so cannot call a private constructor. + @ref copy_construct_tag is private, though, and nothing in the + public interface hands out a value of it, so outside code can still + never name it to call this constructor itself. + */ + basic_json(copy_construct_tag /*unused*/, const basic_json& src) + : json_base_class_t(src) #if JSON_DIAGNOSTIC_POSITIONS - dst.start_position = src.start_position; - dst.end_position = src.end_position; + , start_position(src.start_position) + , end_position(src.end_position) #endif + { } + private: + /*! @brief copy the value of @a src into @a dst, which must not be structured @@ -27833,8 +27853,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } /*! - @brief copy everything of @a src into the null value @a dst but the children + @brief finish the copy @a dst of @a src that a @ref copy_construct_tag + constructor started, other than the children of an object or array + @a dst already has @a src's base class and, with @ref + JSON_DIAGNOSTIC_POSITIONS, positions; only its value is still missing. Objects and arrays are not copied here; they are appended to @a worklist to be created later by @ref copy_iteratively. Until that happens, @a dst remains a null value, so that a partially built copy can be destroyed at any point @@ -27842,8 +27865,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec */ static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist) { - copy_metadata(src, dst); - if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array) { // defer: dst stays a null value until its container exists @@ -27864,15 +27885,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { const array_t& src_array = *src.m_data.m_value.array; - // create all elements up front: growing the array afterwards could - // invalidate the pointers that are handed to the worklist; resize() - // rather than the fill constructor, because not every array type - // provides the latter (e.g., ones without a matching allocator-aware - // fill constructor) dst.m_data.m_value.array = create(); // only now that the array exists may dst stop being a null value dst.m_data.m_type = value_t::array; - dst.m_data.m_value.array->resize(src_array.size()); + + // create every element - its base class already copy-constructed from + // its counterpart in src, via the copy_construct_tag constructor - + // before any of their addresses are handed to worklist below: growing + // the array while that is going on could reallocate it and invalidate + // addresses taken from an earlier iteration + for (const auto& src_element : src_array) + { + dst.m_data.m_value.array->emplace_back(copy_construct_tag{}, src_element); + } auto dst_it = dst.m_data.m_value.array->begin(); for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it) @@ -27890,12 +27915,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // build the complete key skeleton and hand it to the object's range // constructor: adding the keys one by one would be quadratic for object - // types that are backed by a vector, such as nlohmann::ordered_map + // types that are backed by a vector, such as nlohmann::ordered_map; each + // value's base class is already copy-constructed from its counterpart + // in src, via the copy_construct_tag constructor scratch.clear(); scratch.reserve(src_object.size()); for (const auto& element : src_object) { - scratch.emplace_back(element.first, basic_json()); + scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second)); } dst.m_data.m_value.object = create(std::make_move_iterator(scratch.begin()), diff --git a/tests/src/unit-custom-base-class.cpp b/tests/src/unit-custom-base-class.cpp index 138940d3d..a6b9b9ea4 100644 --- a/tests/src/unit-custom-base-class.cpp +++ b/tests/src/unit-custom-base-class.cpp @@ -405,3 +405,73 @@ TEST_CASE("JSON Visit Node") ); CHECK(expected.empty()); } + +// A custom base class with a const member: copy-constructible (initializing a +// const member works fine), but not copy-/move-assignable (assigning one does +// not). Used to check that copy construction never requires more than that. +struct const_member_base +{ + const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +using json_with_const_base = nlohmann::basic_json < + std::map, + std::vector, + std::string, + bool, + std::int64_t, + std::uint64_t, + double, + std::allocator, + nlohmann::adl_serializer, + std::vector, + const_member_base + >; + +// build an array nested @a depth levels deep, with the innermost value 1; +// every level is constructed (never assigned), since const_member_base does +// not support assignment +static json_with_const_base make_nested_array(std::size_t depth) +{ + if (depth == 0) + { + return json_with_const_base(1); + } + return json_with_const_base::array({make_nested_array(depth - 1)}); +} + +TEST_CASE("Regression test for issue #5674 - copy construction must not require an assignable base class") +{ + SECTION("depth 0") + { + // as in the original bug report: copy construction only, no assignment + const json_with_const_base j = {1, 2}; + const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization) + + CHECK(copy.size() == 2); + CHECK(copy.id == 7); + } + + SECTION("nested deeper than the copy constructor's descent bound") + { + // beyond nesting_depth_limit() (128) levels, the copy constructor + // copies without the call stack (copy_iteratively / copy_array_level), + // which used to assign the base class of every element it created + const std::size_t depth = 300; + + const json_with_const_base j = make_nested_array(depth); + const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization) + + const json_with_const_base* c = © + for (std::size_t level = 0; level <= depth; ++level) + { + CAPTURE(level) + REQUIRE(c->id == 7); + if (level < depth) + { + c = &c->at(0); + } + } + CHECK(*c == 1); + } +} From 756b28c2b85d668c9e74e3fc3c4688039ebc2901 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:39 +0200 Subject: [PATCH 14/27] Keep a NUL byte ending a // comment as the end of input (#5696) With the default NUL handling (JSON_STRICT_NUL_HANDLING not set), a NUL byte in the input is treated as the real end of input everywhere - except when it immediately ends a `//` comment: scan_comment() matched '\0' as a comment terminator like '\n', so the NUL was consumed as part of the comment and scan() never saw it as end of input; the next get() then kept reading past it. Multi-line comments and JSON_STRICT_NUL_HANDLING=1 were unaffected, since there the NUL is just part of the comment text. Fix scan_comment() to leave the NUL unconsumed (unget()) instead of returning it as part of the comment, so the following scan() reports it as end of input, exactly as for a NUL anywhere else. Fixes #5659. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/input/lexer.hpp | 7 ++++- single_include/nlohmann/json.hpp | 7 ++++- tests/src/unit-class_parser.cpp | 39 +++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index a67a0228c..052bc4de9 100644 --- a/include/nlohmann/detail/input/lexer.hpp +++ b/include/nlohmann/detail/input/lexer.hpp @@ -941,10 +941,15 @@ class lexer : public lexer_base case '\n': case '\r': case char_traits::eof(): + return true; + #if !JSON_STRICT_NUL_HANDLING case '\0': -#endif + // a NUL byte is the end of the input (see scan()), + // so leave it for scan() to see + unget(); return true; +#endif default: break; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 349977636..884430274 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11027,10 +11027,15 @@ class lexer : public lexer_base case '\n': case '\r': case char_traits::eof(): + return true; + #if !JSON_STRICT_NUL_HANDLING case '\0': -#endif + // a NUL byte is the end of the input (see scan()), + // so leave it for scan() to see + unget(); return true; +#endif default: break; diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index f67ba631b..75f3757e8 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -592,6 +592,45 @@ TEST_CASE("parser class") // parsing from a string literal is unaffected either way CHECK(json::parse("123") == json(123)); + + // a NUL byte that ends a // comment ends the input just + // like a NUL byte anywhere else (issue #5659); before the + // fix, the NUL was consumed as part of the comment, and + // scanning continued with whatever followed it + { + // same as "//c" alone (real end of input after the + // comment), rather than continuing with "[1]" + std::string s1 = "//c"; + s1.push_back('\0'); + s1 += "[1]"; + json _; // NOLINT(readability-identifier-naming) + CHECK_THROWS_WITH_AS(_ = json::parse(s1, nullptr, true, true), + "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", + json::parse_error&); + CHECK_FALSE(json::accept(s1, true, true)); + } + + { + // same as "[1, //c" alone, rather than continuing with " 2]" + std::string s2 = "[1, //c"; + s2.push_back('\0'); + s2 += " 2]"; + json _; // NOLINT(readability-identifier-naming) + CHECK_THROWS_WITH_AS(_ = json::parse(s2, nullptr, true, true), + "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", + json::parse_error&); + CHECK_FALSE(json::accept(s2, true, true)); + } + + { + // same as "1 //c" alone: the comment (and the NUL that + // ends it) is ignored, and "x" is never reached + std::string s3 = "1 //c"; + s3.push_back('\0'); + s3 += "x"; + CHECK(json::parse(s3, nullptr, true, true) == json(1)); + CHECK(json::accept(s3, true, true)); + } } #endif From 1edf0ef041fe20710cea8d236a4bab05d53b0fce Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:43 +0200 Subject: [PATCH 15/27] Fix value(json_pointer, default) aborting under JSON_NOEXCEPTION (#5700) With exceptions disabled (JSON_NOEXCEPTION or -fno-exceptions), value(const json_pointer&, default) called std::abort() for array reference tokens that array_index() rejects with out_of_range.404/410: indices too large to fit size_type, the empty token ("/"), and tokens like "/1a". With exceptions enabled, the same tokens correctly yielded the default value, because get_checked_or_null() relied on JSON_TRY/JSON_INTERNAL_CATCH (detail::out_of_range&) to turn the exception into nullptr; under JSON_NOEXCEPTION, JSON_THROW aborts before that catch is ever reached. get_checked_or_null() now detects those out-of-range tokens itself, the same way contains(json_pointer) already does (#5495), and only calls array_index() for tokens that must still raise parse_error.106 or parse_error.109 (e.g. "/01", "/+1"), matching the documented behavior of value(). Added regression tests to tests/src/unit-disabled_exceptions.cpp (built with JSON_NOEXCEPTION and -fno-exceptions) and the matching checks to tests/src/unit-element_access2.cpp for normal exception mode. Fixes #5672. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 26 ++++++++++++++++-------- single_include/nlohmann/json.hpp | 26 ++++++++++++++++-------- tests/src/unit-disabled_exceptions.cpp | 15 ++++++++++++++ tests/src/unit-element_access2.cpp | 15 ++++++++++++++ 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index e34acb6d3..a10f4f39f 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -685,19 +685,29 @@ class json_pointer return nullptr; } - // may throw parse_error.106/109 for a malformed index; an - // index that is syntactically valid but cannot be - // represented (out_of_range.404/410) is treated like an - // out-of-range index below - typename BasicJsonType::size_type idx{}; - JSON_TRY + // tokens that array_index() rejects with parse_error.106/109 + // are passed on to it; all other tokens that it would reject + // with out_of_range.404/410 are detected here, so that this + // also works without exceptions + if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 && !(reference_token[0] >= '1' && reference_token[0] <= '9'))) { - idx = array_index(reference_token); + static_cast(array_index(reference_token)); // throws parse_error.106/109 } - JSON_INTERNAL_CATCH (detail::out_of_range&) + if (JSON_HEDLEY_UNLIKELY(reference_token.empty() || !std::all_of(reference_token.begin(), reference_token.end(), [](const char c) + { + return c >= '0' && c <= '9'; + }))) { return nullptr; } + errno = 0; // strtoull() does not reset errno on success + char* p_end = nullptr; // NOLINT(misc-const-correctness) + const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int) + if (JSON_HEDLEY_UNLIKELY(errno == ERANGE || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) + { + return nullptr; + } + const auto idx = static_cast(magnitude); if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 884430274..1ec6e9cc5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20281,19 +20281,29 @@ class json_pointer return nullptr; } - // may throw parse_error.106/109 for a malformed index; an - // index that is syntactically valid but cannot be - // represented (out_of_range.404/410) is treated like an - // out-of-range index below - typename BasicJsonType::size_type idx{}; - JSON_TRY + // tokens that array_index() rejects with parse_error.106/109 + // are passed on to it; all other tokens that it would reject + // with out_of_range.404/410 are detected here, so that this + // also works without exceptions + if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 && !(reference_token[0] >= '1' && reference_token[0] <= '9'))) { - idx = array_index(reference_token); + static_cast(array_index(reference_token)); // throws parse_error.106/109 } - JSON_INTERNAL_CATCH (detail::out_of_range&) + if (JSON_HEDLEY_UNLIKELY(reference_token.empty() || !std::all_of(reference_token.begin(), reference_token.end(), [](const char c) + { + return c >= '0' && c <= '9'; + }))) { return nullptr; } + errno = 0; // strtoull() does not reset errno on success + char* p_end = nullptr; // NOLINT(misc-const-correctness) + const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int) + if (JSON_HEDLEY_UNLIKELY(errno == ERANGE || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) + { + return nullptr; + } + const auto idx = static_cast(magnitude); if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { diff --git a/tests/src/unit-disabled_exceptions.cpp b/tests/src/unit-disabled_exceptions.cpp index 0b8de64d3..8e3adf944 100644 --- a/tests/src/unit-disabled_exceptions.cpp +++ b/tests/src/unit-disabled_exceptions.cpp @@ -47,6 +47,21 @@ TEST_CASE("Tests with disabled exceptions") delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory) } + SECTION("issue #5672 - value(json_pointer, default) must not abort for array tokens that are not a valid index") + { + const json j = {1, 2, 3}; + + // a syntactically valid index that is out of range for this array + CHECK(j.value("/7"_json_pointer, 42) == 42); + // a reference token that is not a number at all + CHECK(j.value("/1a"_json_pointer, 42) == 42); + // the empty reference token (JSON pointer "/") + CHECK(j.value("/"_json_pointer, 42) == 42); + // an index whose magnitude does not fit into size_type + CHECK(j.value("/99999999999999999999999"_json_pointer, 42) == 42); + CHECK(j.value("/18446744073709551615"_json_pointer, 42) == 42); + } + SECTION("growing an ordered_json object") { auto j = nlohmann::ordered_json::object(); diff --git a/tests/src/unit-element_access2.cpp b/tests/src/unit-element_access2.cpp index a64caff89..efd7b15a0 100644 --- a/tests/src/unit-element_access2.cpp +++ b/tests/src/unit-element_access2.cpp @@ -516,6 +516,21 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j CHECK(j_array.value("/-"_json_pointer, 42) == 42); CHECK(j_array_const.value("/-"_json_pointer, 42) == 42); + // Test an index with a non-digit after a valid leading digit; this is + // out_of_range (not parse_error) and must not throw (see #5672) + CHECK(j_array.value("/1a"_json_pointer, 42) == 42); + CHECK(j_array_const.value("/1a"_json_pointer, 42) == 42); + + // Test the empty reference token (JSON pointer "/"); see #5672 + CHECK(j_array.value("/"_json_pointer, 42) == 42); + CHECK(j_array_const.value("/"_json_pointer, 42) == 42); + + // Test an index whose magnitude does not fit into size_type (see #5672) + CHECK(j_array.value("/99999999999999999999999"_json_pointer, 42) == 42); + CHECK(j_array_const.value("/99999999999999999999999"_json_pointer, 42) == 42); + CHECK(j_array.value("/18446744073709551615"_json_pointer, 42) == 42); + CHECK(j_array_const.value("/18446744073709551615"_json_pointer, 42) == 42); + #if !defined(JSON_NOEXCEPTION) // Test malformed index (non-numeric) throws parse_error CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&); From a212d3b2e4818849a57270bb537d74564be77692 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:47 +0200 Subject: [PATCH 16/27] Preserve the object comparator's state in a deep copy past the nesting bound (#5722) * Preserve the object comparator's state in a deep copy past the nesting bound copy_object_level(), used by the copy constructor and copy assignment once a value is nested deeper than the iterative deep copy's bound (128 levels, or every copy under JSON_NO_THREAD_LOCAL), built each object's copy with the object type's plain range constructor. That default-constructs the object's comparator instead of copying the original's. For an object type whose comparator carries state, such as a std::map that compares keys case-sensitively only when constructed that way, the copy then ordered - and could even deduplicate - its keys differently from the original. Add detail::is_comparator_constructible_object_type, a detection trait for object types that provide a key_comp() and a constructor taking a range and a comparator, the way std::map does. copy_object_level now dispatches on it: an object type that qualifies gets its copy built with src_object.key_comp() passed along; other object types, such as nlohmann::ordered_map (which has a key_compare for its std::map-like interface, but no key_comp()), keep using the plain range constructor exactly as before. merge_patch and update() were checked for the same pattern; neither is affected, since both only ever add members one at a time to an object that already has its own comparator (or start a brand new default-constructed one), rather than rebuilding an object_t from a range copied out of an existing, possibly custom-comparator object. Fixes #5649. Signed-off-by: Niels Lohmann * Keep astyle from padding the create_object_with_comparator templates Spell the negated condition as detail::negation<...> instead of a leading '!', which made astyle spread the template header out. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- include/nlohmann/detail/meta/type_traits.hpp | 31 ++++++++ include/nlohmann/json.hpp | 24 +++++- single_include/nlohmann/json.hpp | 55 ++++++++++++- tests/src/unit-comparison.cpp | 81 ++++++++++++++++++++ 4 files changed, 189 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 2f837e046..14029c0fc 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -189,6 +189,37 @@ struct actual_object_comparator template using actual_object_comparator_t = typename actual_object_comparator::type; +template +using detect_key_comp = decltype(std::declval().key_comp()); + +// whether ObjectType can be constructed from a pair of Iterator together with +// a copy of its own comparator, the way std::map can: it needs a nested +// key_compare, a const key_comp() convertible to it, and a matching +// (Iterator, Iterator, const key_compare&) constructor. +// +// used to preserve a stateful comparator when a copy is built from a range +// past the iterative deep copy's nesting bound (see copy_object_level); an +// object type that does not satisfy this, such as nlohmann::ordered_map +// (which has key_compare for its std::map-like interface, but no key_comp()), +// keeps default-constructing its comparator, just as it always has +template +struct is_comparator_constructible_object_type_impl : std::false_type {}; + +template +struct is_comparator_constructible_object_type_impl < + ObjectType, Iterator, enable_if_t::value >> +{ + using key_compare = typename ObjectType::key_compare; + + static constexpr bool value = + is_detected_convertible::value && + std::is_constructible::value; +}; + +template +struct is_comparator_constructible_object_type + : is_comparator_constructible_object_type_impl {}; + ///////////////// // char_traits // ///////////////// diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 268c28c42..c8c9854ca 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1176,6 +1176,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } } + /// @brief create the object type from a range, preserving @a src_object's + /// comparator when the object type supports it + /// Enabled for object types that provide a key_comp() and a matching + /// range-plus-comparator constructor, such as std::map. Other object + /// types, such as nlohmann::ordered_map, fall back to the plain range + /// constructor and default-construct their comparator, just as they + /// always have (@ref detail::is_comparator_constructible_object_type). + template::value, int> = 0> + static object_t* create_object_with_comparator(const object_t& src_object, Iterator first, Iterator last) + { + return create(first, last, src_object.key_comp()); + } + + template>::value, int> = 0> + static object_t* create_object_with_comparator(const object_t& /*src_object*/, Iterator first, Iterator last) + { + return create(first, last); + } + /// @brief create the copy of the object @a src in @a dst /// @note structured values are appended to @a worklist instead static void copy_object_level(const basic_json& src, basic_json& dst, @@ -1195,7 +1216,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second)); } - dst.m_data.m_value.object = create(std::make_move_iterator(scratch.begin()), + dst.m_data.m_value.object = create_object_with_comparator(src_object, + std::make_move_iterator(scratch.begin()), std::make_move_iterator(scratch.end())); // only now that the object exists may dst stop being a null value dst.m_data.m_type = value_t::object; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 1ec6e9cc5..84ad40c74 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4189,6 +4189,37 @@ struct actual_object_comparator template using actual_object_comparator_t = typename actual_object_comparator::type; +template +using detect_key_comp = decltype(std::declval().key_comp()); + +// whether ObjectType can be constructed from a pair of Iterator together with +// a copy of its own comparator, the way std::map can: it needs a nested +// key_compare, a const key_comp() convertible to it, and a matching +// (Iterator, Iterator, const key_compare&) constructor. +// +// used to preserve a stateful comparator when a copy is built from a range +// past the iterative deep copy's nesting bound (see copy_object_level); an +// object type that does not satisfy this, such as nlohmann::ordered_map +// (which has key_compare for its std::map-like interface, but no key_comp()), +// keeps default-constructing its comparator, just as it always has +template +struct is_comparator_constructible_object_type_impl : std::false_type {}; + +template +struct is_comparator_constructible_object_type_impl < + ObjectType, Iterator, enable_if_t::value >> +{ + using key_compare = typename ObjectType::key_compare; + + static constexpr bool value = + is_detected_convertible::value && + std::is_constructible::value; +}; + +template +struct is_comparator_constructible_object_type + : is_comparator_constructible_object_type_impl {}; + ///////////////// // char_traits // ///////////////// @@ -27921,6 +27952,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } } + /// @brief create the object type from a range, preserving @a src_object's + /// comparator when the object type supports it + /// Enabled for object types that provide a key_comp() and a matching + /// range-plus-comparator constructor, such as std::map. Other object + /// types, such as nlohmann::ordered_map, fall back to the plain range + /// constructor and default-construct their comparator, just as they + /// always have (@ref detail::is_comparator_constructible_object_type). + template::value, int> = 0> + static object_t* create_object_with_comparator(const object_t& src_object, Iterator first, Iterator last) + { + return create(first, last, src_object.key_comp()); + } + + template>::value, int> = 0> + static object_t* create_object_with_comparator(const object_t& /*src_object*/, Iterator first, Iterator last) + { + return create(first, last); + } + /// @brief create the copy of the object @a src in @a dst /// @note structured values are appended to @a worklist instead static void copy_object_level(const basic_json& src, basic_json& dst, @@ -27940,7 +27992,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second)); } - dst.m_data.m_value.object = create(std::make_move_iterator(scratch.begin()), + dst.m_data.m_value.object = create_object_with_comparator(src_object, + std::make_move_iterator(scratch.begin()), std::make_move_iterator(scratch.end())); // only now that the object exists may dst stop being a null value dst.m_data.m_type = value_t::object; diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index 4b66f1d07..15715fec2 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -849,6 +849,46 @@ Json nest(Json j, const std::size_t depth) return j; } +// a std::map comparator with state: case-insensitive, unless constructed +// case-sensitive. Used to check that copying an object copies the original's +// comparator rather than default-constructing a new one (see #5649). +struct key_case_less +{ + key_case_less() = default; + explicit key_case_less(const bool cs) noexcept : case_sensitive(cs) {} + + bool operator()(const std::string& a, const std::string& b) const + { + if (case_sensitive) + { + return a < b; + } + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), + [](unsigned char x, unsigned char y) + { + return std::tolower(x) < std::tolower(y); + }); + } + + bool case_sensitive = false; +}; + +template +using key_case_map = std::map; +using key_case_json = nlohmann::basic_json; + +// the innermost value of a chain of single-element arrays +template +const Json& innermost(const Json& j) +{ + const Json* p = &j; + while (p->is_array()) + { + p = &(*p)[0]; + } + return *p; +} + // orders keys case-insensitively, so "key" and "KEY" compare equivalent // (neither less than the other) although they are not equal struct case_insensitive_less @@ -913,6 +953,47 @@ TEST_CASE("equality of objects whose entries have no fixed order") } } +TEST_CASE("copying an object preserves its comparator's state") +{ + // Past the iterative deep copy's nesting bound, an object copy used to be + // built with a default-constructed comparator instead of a copy of the + // original's. For an object type whose comparator carries state - here, a + // std::map that compares keys case-sensitively only when created that way + // - this reordered the copy's keys and could even drop entries that the + // original's comparator kept distinct (see #5649). + key_case_json object = key_case_json::object_t(key_case_less(true)); // case-sensitive + object["b"] = 1; + object["B"] = 2; + object["a"] = 3; + REQUIRE(object.dump() == R"({"B":2,"a":3,"b":1})"); + + for (const std::size_t depth : std::vector {0, 127, 128, 200}) + { + CAPTURE(depth); + + key_case_json original = object; + for (std::size_t i = 0; i < depth; ++i) + { + original = key_case_json::array({std::move(original)}); + } + + { + const key_case_json copy = original; // NOLINT(performance-unnecessary-copy-initialization) + CHECK(innermost(copy).size() == 3); + CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})"); + CHECK(copy == original); + } + + { + key_case_json copy = key_case_json::array(); + copy = original; + CHECK(innermost(copy).size() == 3); + CHECK(innermost(copy).dump() == R"({"B":2,"a":3,"b":1})"); + CHECK(copy == original); + } + } +} + TEST_CASE("equality of an object whose comparator treats different keys as equivalent") { // https://github.com/nlohmann/json/issues/5655: past the nesting bound, From 1df1e8a845350f1156f8b4ac24887d3f40b4e71a Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:46:52 +0200 Subject: [PATCH 17/27] Make cross-string-type basic_json conversion explicit without implicit conversions (#5591) * Make cross-string-type basic_json conversion explicit without implicit conversions The converting constructor from another basic_json specialization was always implicit, so a value with a different string_t (std::wstring, a string with a custom allocator, ...) silently converted into a temporary, e.g. when passed to a function taking const nlohmann::json&. Such conversions do not produce correct values (#3425), and JSON_USE_IMPLICIT_CONVERSIONS=0 did not catch them. When JSON_USE_IMPLICIT_CONVERSIONS is 0, the constructor is now explicit if the string types differ. Specializations sharing a string type (json and ordered_json, different serializers or object maps) stay implicitly convertible, so the NLOHMANN_DEFINE_TYPE_* macros keep working with nested json members. get() constructs explicitly and works in both modes. Fixes #2649. Signed-off-by: Niels Lohmann * Construct explicitly in get_to() and to_json(std::optional) With JSON_USE_IMPLICIT_CONVERSIONS=0 the conversion from a basic_json with a different string type is now explicit, but two library paths still assigned such a value implicitly and failed to compile inside the library: - get_to() with a basic_json target (the #2175 overload) did `v = *this`, so json(42).get_to(alt_json&) broke although get() works. - to_json(BasicJsonType&, const std::optional&) is constrained on std::is_constructible (which accepts the explicit constructor) but did `j = *opt`, so converting a std::optional into a json broke. Both now construct the value explicitly, as get_impl() already does. Also replace static_cast(JSON_USE_IMPLICIT_CONVERSIONS) with a comparison: clang-tidy's modernize-use-bool-literals rejected the cast of the integer literal the macro expands to, failing ci_clang_tidy. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/basic_json.md | 12 +++++- .../macros/json_use_implicit_conversions.md | 24 ++++++++++- .../nlohmann/detail/conversions/to_json.hpp | 4 +- include/nlohmann/json.hpp | 37 +++++++++++++++-- single_include/nlohmann/json.hpp | 41 +++++++++++++++++-- tests/src/unit-alt-string.cpp | 36 ++++++++++++++++ 6 files changed, 144 insertions(+), 10 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md index 1a0b101bd..78fa3560c 100644 --- a/docs/mkdocs/docs/api/basic_json/basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/basic_json.md @@ -293,6 +293,15 @@ basic_json(basic_json&& other) noexcept; When used without parentheses around an empty initializer list, `basic_json()` is called instead of this function, yielding the JSON `#!json null` value. +- Overload 4: + + !!! info "Implicit conversion" + + The conversion is implicit unless [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) + is defined to `0` and `BasicJsonType::string_t` differs from `string_t`. In that case, the constructor is + `explicit`, so a JSON value with a different string type is no longer silently converted, for example when it is + passed to a function taking `#!cpp const json&`. Write `#!cpp json(other)` or `#!cpp other.get()` instead. + - Overload 7: !!! info "Preconditions" @@ -466,7 +475,8 @@ basic_json(basic_json&& other) noexcept; 1. Since version 1.0.0. 2. Since version 1.0.0. 3. Since version 2.1.0. -4. Since version 3.2.0. +4. Since version 3.2.0. Explicit for different string types if `JSON_USE_IMPLICIT_CONVERSIONS` is `0` since + version 3.13.0. 5. Since version 1.0.0. 6. Since version 1.0.0. 7. Since version 1.0.0. Fixed in version 3.13.0 to also check the iterator range for binary values; before, a range diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index 5b74f1ff7..11bf74b22 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -5,7 +5,9 @@ ``` When defined to `0`, implicit conversions are switched off. By default, implicit conversions are switched on. The -value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md). +value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md) and the +[converting constructor](../basic_json/basic_json.md) from a `basic_json` specialization with a different string +type (overload 4). ## Default definition @@ -59,6 +61,25 @@ By default, implicit conversions are enabled. auto s = j.get(); ``` +??? example "Conversion between `basic_json` specializations" + + A `basic_json` specialization with a different string type is also no longer converted implicitly when + `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`: + + ```cpp + using wjson = nlohmann::basic_json; + + void load(const nlohmann::json& j); + + wjson wj = /* ... */; + load(wj); // error: no implicit conversion + load(nlohmann::json(wj)); // OK: explicit conversion + load(wj.get()); // OK: explicit conversion + ``` + + Specializations that share the same string type, such as `json` and `ordered_json`, remain implicitly + convertible. + ## See also - [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit) @@ -68,3 +89,4 @@ By default, implicit conversions are enabled. ## Version history - Added in version 3.9.0. +- Also affects the conversion between `basic_json` specializations with different string types since version 3.13.0. diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 49b3c32e2..7b97068f3 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -294,7 +294,9 @@ void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_not { if (opt.has_value()) { - j = *opt; + // explicit construction, as the conversion from a basic_json with a different + // string type is explicit if JSON_USE_IMPLICIT_CONVERSIONS is 0 (#2649) + j = BasicJsonType(*opt); } else { diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index c8c9854ca..87e1dbc04 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1950,12 +1950,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + private: + /// whether a basic_json specialization can be converted implicitly into this one; + /// with JSON_USE_IMPLICIT_CONVERSIONS set to 0, this is only the case if both share + /// the same string type (see https://github.com/nlohmann/json/issues/2649) + template + using is_implicitly_convertible_basic_json = std::integral_constant < bool, + (JSON_USE_IMPLICIT_CONVERSIONS != 0) + || std::is_same::value >; + + /// tag to select the constructor that performs the conversion from another basic_json specialization + struct convert_basic_json_tag {}; + + public: /// @brief create a JSON value from an existing one /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename BasicJsonType, detail::enable_if_t < - detail::is_basic_json::value&& !std::is_same::value, int > = 0 > + detail::is_basic_json::value&& !std::is_same::value + && is_implicitly_convertible_basic_json::value, int > = 0 > basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + /// @brief create a JSON value from an existing one + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::value + && !is_implicitly_convertible_basic_json::value, int > = 0 > + explicit basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + private: + template + basic_json(const BasicJsonType& val, convert_basic_json_tag /*unused*/) #if JSON_DIAGNOSTIC_POSITIONS : start_position(val.start_pos()), end_position(val.end_pos()) @@ -1975,6 +2005,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + public: /// @brief create a container (array or object) from an initializer list /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(initializer_list_t init, @@ -2741,7 +2772,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int > = 0 > BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const { - return *this; + return BasicJsonType(*this); } /*! @@ -2880,7 +2911,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int> = 0> ValueType & get_to(ValueType& v) const { - v = *this; + v = ValueType(*this); return v; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 84ad40c74..a91bd9361 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6960,7 +6960,9 @@ void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_not { if (opt.has_value()) { - j = *opt; + // explicit construction, as the conversion from a basic_json with a different + // string type is explicit if JSON_USE_IMPLICIT_CONVERSIONS is 0 (#2649) + j = BasicJsonType(*opt); } else { @@ -28726,12 +28728,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + private: + /// whether a basic_json specialization can be converted implicitly into this one; + /// with JSON_USE_IMPLICIT_CONVERSIONS set to 0, this is only the case if both share + /// the same string type (see https://github.com/nlohmann/json/issues/2649) + template + using is_implicitly_convertible_basic_json = std::integral_constant < bool, + (JSON_USE_IMPLICIT_CONVERSIONS != 0) + || std::is_same::value >; + + /// tag to select the constructor that performs the conversion from another basic_json specialization + struct convert_basic_json_tag {}; + + public: /// @brief create a JSON value from an existing one /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename BasicJsonType, detail::enable_if_t < - detail::is_basic_json::value&& !std::is_same::value, int > = 0 > + detail::is_basic_json::value&& !std::is_same::value + && is_implicitly_convertible_basic_json::value, int > = 0 > basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + /// @brief create a JSON value from an existing one + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::value + && !is_implicitly_convertible_basic_json::value, int > = 0 > + explicit basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + private: + template + basic_json(const BasicJsonType& val, convert_basic_json_tag /*unused*/) #if JSON_DIAGNOSTIC_POSITIONS : start_position(val.start_pos()), end_position(val.end_pos()) @@ -28751,6 +28783,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + public: /// @brief create a container (array or object) from an initializer list /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(initializer_list_t init, @@ -29517,7 +29550,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int > = 0 > BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const { - return *this; + return BasicJsonType(*this); } /*! @@ -29656,7 +29689,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int> = 0> ValueType & get_to(ValueType& v) const { - v = *this; + v = ValueType(*this); return v; } diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index 8b86c78e8..e503a4914 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -423,6 +424,41 @@ TEST_CASE("alternative string type") CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})"); } + SECTION("conversion between basic_json specializations (#2649)") + { + // explicit conversions are always possible + CHECK(std::is_constructible::value); + CHECK(std::is_constructible::value); + CHECK(std::is_constructible::value); + CHECK(std::is_constructible::value); + + // specializations with the same string type are implicitly convertible + CHECK(std::is_convertible::value); + CHECK(std::is_convertible::value); + + // specializations with different string types are only implicitly convertible + // if implicit conversions are enabled +#if JSON_USE_IMPLICIT_CONVERSIONS + CHECK(std::is_convertible::value); + CHECK(std::is_convertible::value); +#else + CHECK_FALSE(std::is_convertible::value); + CHECK_FALSE(std::is_convertible::value); +#endif + + // get() works in either case + const nlohmann::json j = {{"foo", 1}, {"bar", true}}; + CHECK(j.get() == nlohmann::ordered_json(j)); + // (only a number is converted here, as objects and strings are affected by #3425) + CHECK(nlohmann::json(42).get() == 42); + CHECK(alt_json(nlohmann::json(42)) == 42); + + // get_to() also works in either case + alt_json a; + nlohmann::json(42).get_to(a); + CHECK(a == 42); + } + SECTION("strict enum") { // regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json From b9850740b965b0c6ff126400f6ac52a0ec577ae1 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:48:47 +0200 Subject: [PATCH 18/27] Add regression test for converting json to std::variant (#5595) * Add regression test for converting json to std::variant (#5066) With 3.10.5, get>() was well-formed through the string from_json overload, so the implicit conversion operator was a candidate when converting json to std::variant, and MSVC picked it over the variant's converting constructor. The tightened constraints from #3427 and #3604 (3.11.0) removed that path; this test guards against regressions. Signed-off-by: Niels Lohmann * Fix clang-tidy and clang 6 in the #5066 regression test ci_clang_tidy asked for emplace_back instead of push_back. The push_back is the point of the test: #5066 is about the implicit conversion from json to the vector's value type, which emplace_back would bypass. Silence the check on that line. clang 5 and 6 cannot instantiate std::variant from libstdc++ 10's ("cannot cast private base class"), which broke ci_test_compilers_clang (6). Tested with the CI images: clang 7 to 11 compile and pass, including clang 11 with libstdc++ 10. Skip the runtime check for clang before 7; the static_assert still runs. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- tests/src/unit-regression2.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 1ea5ac595..79ca5a770 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -808,6 +808,26 @@ TEST_CASE("regression tests 2") CHECK(j == k); } +#ifdef JSON_HAS_CPP_17 + SECTION("issue #5066 - MSVC converts json to std::variant via the conversion operator") + { + // std::variant must not be retrievable via get<>(), because otherwise the + // implicit conversion operator becomes a candidate that MSVC picks over the variant's + // converting constructor, routing a number through the string from_json overload + static_assert(!nlohmann::detail::is_detected>::value, + "std::variant must not be retrievable via get<>()"); + + // clang before 7 cannot instantiate libstdc++'s std::variant +#if !(defined(__clang__) && __clang_major__ < 7) + // push_back, not emplace_back: #5066 needs the implicit conversion + // from json to the vector's value type + std::vector> v; + v.push_back(json(1)); // NOLINT(hicpp-use-emplace,modernize-use-emplace) + CHECK(std::get<0>(v[0]) == 1); +#endif + } +#endif + SECTION("issue #3669 - invalid use of incomplete type with optional member and to_json") { const Issue3669Holder h{}; From 38a2db260c48efc4887613b412a8c941594988fa Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:50:42 +0200 Subject: [PATCH 19/27] Remove dead metaprogramming and duplicated code in traits and pointers (#5728) * Remove unused is_sax and is_detected_convertible detail::is_sax had no user: the parser and the binary reader only use is_sax_static_asserts, so is_sax was a second, unchecked copy of the SAX event list. is_sax_static_asserts asserted boolean(bool) twice in a row, and detail::is_detected_convertible was never used anywhere. Remove all three and include for size_t instead of . Only names in nlohmann::detail are removed; behavior, public API and ABI are unchanged. The diagnostics for an incomplete SAX handler are the same, apart from the duplicated boolean() message. Part of #5708 Signed-off-by: Niels Lohmann * Replace meta/logic.hpp with a disjunction trait meta/logic.hpp added a second set of type-level boolean helpers (cxpr_and, cxpr_or, cxpr_not, ...) next to the existing conjunction and negation in type_traits.hpp. It was used only by one static_assert in from_json_tuple_impl, two of its templates were never used, and it was the only header without the license banner and relied on transitive includes for . Add the missing disjunction next to conjunction and negation, use the three in the static_assert, and delete logic.hpp together with its BUILD.bazel entry. same_sign now uses disjunction as well, which resolves the 2022 TODO waiting for such a trait. The static_assert accepts and rejects the same types as before. Only names in nlohmann::detail change; behavior, public API and ABI are unchanged. Part of #5708 Signed-off-by: Niels Lohmann * Remove unused would_call_std_* from NLOHMANN_CAN_CALL_STD_FUNC_IMPL Besides detail::result_of_begin/end, which is_range and iterator_t use, the macro defined a namespace detail2 with a tag type, a catch-all overload and would_call_std_begin/end, plus would_call_std_begin/end structs directly in namespace nlohmann. Nothing has used them since they were added in #3020. Reduce the macro to its detail part. Without the trailing struct the ';' after the two invocations would be an empty declaration that -Wextra-semi flags, so drop it. macro_scope.hpp included meta/detected.hpp only for this macro; all users of detected.hpp include it (or type_traits.hpp) themselves, so remove the include. Behavior and ABI are unchanged. The undocumented, untested and unused names nlohmann::would_call_std_begin, nlohmann::would_call_std_end and namespace nlohmann::detail2 are no longer declared. Part of #5708 Signed-off-by: Niels Lohmann * Simplify is_ordered_map to reuse has_capacity is_ordered_map re-detected capacity() with a C++03 sizeof/vararg trick right after has_capacity did the same detection through is_detected. For ordered_map, the old trick took the address of std::vector::capacity, which [namespace.std]/6 makes unspecified. Reuse has_capacity instead, which removes the unspecified-behavior pointer-to-std-member and two NOLINT suppressions. Part of #5708 Signed-off-by: Niels Lohmann * Remove duplicate const overload of json_pointer::get_checked The const and non-const get_checked() overloads had byte-identical 50-line bodies, differing only in the signature. The remaining template deduces a const-qualified BasicJsonType for const callers, so at(), the out_of_range::create() calls and the bounds check all still work. Part of #5708 Signed-off-by: Niels Lohmann * Fix tautological clause in iter_impl's iterator category assertion The static_assert meant to check the LegacyBidirectionalIterator named requirement had a first clause comparing std::bidirectional_iterator_tag to itself, which is always true and checks nothing; only array_t::iterator was actually being checked, despite the message claiming object iterators were checked too. Drop the tautological clause, reword the message to describe what is actually checked, and note that object_t may use a forward-only iterator as long as reverse iteration and operator-- are unused. The check is intentionally not extended to object_t::iterator, since that would reject object types with forward-only iterators that compile and work correctly today. Part of #5708 Signed-off-by: Niels Lohmann * Fix misplaced and stale comments in JSON_HAS_RANGES and conversions The JSON_HAS_RANGES feature-detection block had its libc++ comment sitting above the clang+libstdc++ branch it does not describe, leaving the libc++ branch uncommented and the clang+libstdc++ branch without its own rationale. Move each comment to sit under its own branch, and give the clang+libstdc++ branch (added in issue 5161) its own one-line reason referencing that issue instead of reusing the libc++ branch's comment. Also fix a duplicated-word typo ("in large in large cpp files") in from_json.hpp, drop two unanswered 2017 design questions left as comments in type_traits.hpp and from_json.hpp that no longer reflect open questions, and correct NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's @since tag from 3.12.0 to 3.13.0, the release it was actually introduced in. Part of #5708 Signed-off-by: Niels Lohmann * Support any-rank C arrays in from_json, not just rank 1-4 from_json() for C arrays had four hand-unrolled overloads (rank 1-4, added incrementally in #4262), each with its own nested loops. to_json() already handles any rank recursively, so a rank-5+ C array could be serialized but not read back with get_to()/get<>(). Replace the four overloads with one from_json() SFINAE-constrained on get::type>() existing, forwarding to a pair of mutually recursive from_json_c_array_element() helpers: one assigns a non-array element via get(), the other loops over a array element and recurses one dimension at a time. Each dimension still goes through at(), so type_error.304/out_of_range.401 stay unchanged; ranks 1-4 keep their existing behavior and semantics. Adds rank-5 round-trip and mismatched-shape tests to unit-conversions.cpp. Public API: additive only (rank 5+ C arrays become readable). Signed-off-by: Niels Lohmann #5708 item 1 * Move templated_json_throw into nlohmann::detail templated_json_throw() was defined in macro_scope.hpp, which is included outside NLOHMANN_JSON_NAMESPACE_BEGIN, so the helper leaked into the global namespace as ::templated_json_throw with no ABI tag. Unqualified lookup in NLOHMANN_JSON_SERIALIZE_ENUM_STRICT could then bind to a same-named function declared in the user's own namespace instead, which fails to compile with Clang ("does not name a template"). Move the helper next to the exception classes in exceptions.hpp, inside nlohmann::detail, and call it qualified as ::nlohmann::detail::templated_json_throw<...>(...) from both macro expansion sites. Rewrite the doc comment to give the real reason for the helper (JSON_THROW may expand to code that discards its argument, e.g. when exceptions are disabled) and fix the "supress" typo. templated_json_throw was never released (added by #5151 after v3.12.0), so it can be moved freely. Adds a regression test that expands NLOHMANN_JSON_SERIALIZE_ENUM_STRICT inside a namespace declaring its own templated_json_throw. Public API: no change (::templated_json_throw was an unreleased, unintentional global-namespace leak with no callers relying on its location). Overlaps #5698, which rewrites the same two macro call lines; the overlapping hunks are small and should be trivial to reconcile on rebase. Signed-off-by: Niels Lohmann #5708 item 2 * Factor the repeated JSON_HAS_RANGES/MinGW guard into one macro The std::ranges view conversion (excluded on MinGW because of its incomplete C++20 ranges support, #4916) was gated by the same #if JSON_HAS_RANGES && !defined(__MINGW32__) condition at seven independent sites in to_json.hpp and type_traits.hpp, with the MinGW rationale duplicated in two of them and missing from the rest. Since the sites come in matching pairs (one enables is_compatible_range_view and a view-based overload, the other adds the exclusion to the plain-array-type overload), a drift between any pair would produce an ambiguous or missing overload on exactly one platform. Add JSON_HAS_RANGE_VIEW_CONVERSION next to JSON_HAS_RANGES in macro_scope.hpp, combining both conditions with the #4916 reasoning in one place, #undef it in macro_unscope.hpp, and use it at all seven sites. This does not fold the MinGW check into JSON_HAS_RANGES itself: JSON_HAS_RANGES is user-overridable and also gates the enable_borrowed_range specialization in iteration_proxy.hpp, which is not excluded on MinGW. No behavior or public API change: JSON_HAS_RANGE_VIEW_CONVERSION expands to exactly the condition that was previously written out at each site. Overlaps #5585, #5600 and #3575, which touch the same to_json.hpp and type_traits.hpp lines; the change here is a mechanical search-and-replace of the guard condition and should rebase cleanly. Signed-off-by: Niels Lohmann #5708 item 11 * De-duplicate from_json.hpp's map and array-fallback bodies Several from_json() overload pairs in from_json.hpp were copies of each other, so a fix has to be applied twice (as #5681 already does): - from_json(..., std::map&) and from_json(..., std::unordered_map&) for non-string keys had identical 16-line bodies: array check, m.clear(), pair check loop, m.emplace(...). Route both through a new from_json_pair_array_to_map(j, m) helper. - The from_json_array_impl priority_tag<1> and priority_tag<0> fallbacks ran the same std::transform/std::inserter loop, differing only in ret.reserve(j.size()). Merge them into one body and, modeled on the existing from_json_object_reserve, add a from_json_array_reserve pair so the reserve() call is only made for ConstructibleArrayType that support it. Error ids (type_error.302), messages, diagnostic paths ((at(0)/at(1)) and behavior for types with/without reserve() are unchanged; only the duplication is removed. Public API: no change. Overlaps #5681, which changes the "&j" to "&p" line in both map bodies; the shared helper here should make that a one-line change instead of two on rebase. Signed-off-by: Niels Lohmann #5708 item 5 * Unify json_pointer's three array-index parsers array_index(), contains() and get_checked_or_null() each re-implemented the RFC 6901 array-index rules and the size_type range check: array_index() does the canonical parse and throws; contains() (which must not throw, #5395) re-validates every digit by hand and runs its own strtoull/ERANGE check before calling array_index() anyway, parsing every array token twice; get_checked_or_null() wraps array_index() in JSON_TRY/ JSON_INTERNAL_CATCH (detail::out_of_range&) to turn an unrepresentable index into "not found". Add a single private, noexcept parse_array_index(s, idx) returning an array_index_status (ok / leading_zero / not_a_number / unresolved / exceeds_size_type). array_index() becomes a thin wrapper mapping each status to the existing parse_error.106/109 or out_of_range.404/410; contains() and get_checked_or_null() switch on the status directly. This removes contains()'s digit-validation loop and its second strtoull call, and get_checked_or_null()'s JSON_TRY/JSON_INTERNAL_CATCH. Bugfix as a consequence: get_checked_or_null()'s JSON_TRY/ JSON_INTERNAL_CATCH was dead code under JSON_NOEXCEPTION (JSON_TRY expands to "if(true)" and the catch to "if(false)", so JSON_THROW's std::abort() ran unconditionally), meaning value() and contains() would abort instead of returning the default/false for an out-of-range-sized or oversized array index when exceptions are disabled (#5672). Switching on parse_array_index()'s return value instead of relying on an actual throw/catch fixes this: get_checked_or_null() now returns nullptr for array_index_status::unresolved/exceeds_size_type in every build configuration, and still calls JSON_THROW (aborting under JSON_NOEXCEPTION, as before) only for a malformed index (leading_zero/not_a_number), matching its documented @throw list. All existing error ids, messages and diagnostic paths are unchanged; a few reference tokens that used to fail contains()'s manual per-character validation (e.g. "1a") now fail via array_index_status::unresolved instead, with no observable difference since contains() only returns bool. Adds regression tests to unit-element_access2.cpp's "access on array type" section covering value() with an index that exceeds size_type and one with a trailing non-digit, both of which must yield the default value rather than abort/throw. Public API: no change. Overlaps #5700, #5614 and #5692, which touch the contains() and get_checked_or_null() array hunks; this change replaces those hunks with calls into the new shared parser, so a rebase will need to re-apply their token-handling changes (e.g. the empty-token case) on top of the switch statements here. Signed-off-by: Niels Lohmann #5708 item 4 * Regenerate single_include after merging develop The merge commit kept develop's single_include/nlohmann/json.hpp because make amalgamate saw it as up to date. Signed-off-by: Niels Lohmann * Address review: switch in array_index, drop redundant inline - json_pointer::array_index() dispatches on array_index_status with a switch, matching the other parse_array_index() caller - drop `inline` from the function templates this PR adds or moves in from_json.hpp - reword a comment that described the change rather than the code Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- BUILD.bazel | 1 - .../nlohmann/detail/conversions/from_json.hpp | 144 +-- .../nlohmann/detail/conversions/to_json.hpp | 10 +- include/nlohmann/detail/exceptions.hpp | 21 + .../nlohmann/detail/iterators/iter_impl.hpp | 8 +- include/nlohmann/detail/json_pointer.hpp | 252 +++--- include/nlohmann/detail/macro_scope.hpp | 60 +- include/nlohmann/detail/macro_unscope.hpp | 1 + .../nlohmann/detail/meta/call_std/begin.hpp | 2 +- include/nlohmann/detail/meta/call_std/end.hpp | 2 +- include/nlohmann/detail/meta/is_sax.hpp | 35 +- include/nlohmann/detail/meta/logic.hpp | 54 -- include/nlohmann/detail/meta/type_traits.hpp | 35 +- single_include/nlohmann/json.hpp | 819 +++++++----------- tests/src/unit-conversions.cpp | 59 ++ tests/src/unit-type_traits.cpp | 12 + 16 files changed, 597 insertions(+), 918 deletions(-) delete mode 100644 include/nlohmann/detail/meta/logic.hpp diff --git a/BUILD.bazel b/BUILD.bazel index 03f73fa92..a9b09fd79 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -53,7 +53,6 @@ cc_library( "include/nlohmann/detail/meta/detected.hpp", "include/nlohmann/detail/meta/identity_tag.hpp", "include/nlohmann/detail/meta/is_sax.hpp", - "include/nlohmann/detail/meta/logic.hpp", "include/nlohmann/detail/meta/std_fs.hpp", "include/nlohmann/detail/meta/type_traits.hpp", "include/nlohmann/detail/meta/void_t.hpp", diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index d5c3328ca..5274b9857 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -211,62 +210,29 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) }); } +// element is not itself a C array: read it directly +template +auto from_json_c_array_element(const BasicJsonType& j, T& e) +-> decltype(e = j.template get(), void()) +{ + e = j.template get(); +} + +// element is itself a C array: recurse one dimension at a time, so any rank is supported template -auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) +void from_json_c_array_element(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { for (std::size_t i = 0; i < N; ++i) { - arr[i] = j.at(i).template get(); + from_json_c_array_element(j.at(i), arr[i]); } } -template -auto from_json(const BasicJsonType& j, T (&arr)[N1][N2]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) +template +auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get::type>(), void()) { - for (std::size_t i1 = 0; i1 < N1; ++i1) - { - for (std::size_t i2 = 0; i2 < N2; ++i2) - { - arr[i1][i2] = j.at(i1).at(i2).template get(); - } - } -} - -template -auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) -{ - for (std::size_t i1 = 0; i1 < N1; ++i1) - { - for (std::size_t i2 = 0; i2 < N2; ++i2) - { - for (std::size_t i3 = 0; i3 < N3; ++i3) - { - arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get(); - } - } - } -} - -template -auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) -{ - for (std::size_t i1 = 0; i1 < N1; ++i1) - { - for (std::size_t i2 = 0; i2 < N2; ++i2) - { - for (std::size_t i3 = 0; i3 < N3; ++i3) - { - for (std::size_t i4 = 0; i4 < N4; ++i4) - { - arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get(); - } - } - } - } + from_json_c_array_element(j, arr); } template @@ -286,20 +252,33 @@ auto from_json_array_impl(const BasicJsonType& j, std::array& arr, } } +// reserve() is called through this pair (modeled on from_json_object_reserve) +// so from_json_array_impl below has a single body for both ConstructibleArrayType +// that support reserve() and those that don't. +template +auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/) +-> decltype(arr.reserve(size), void()) +{ + arr.reserve(size); +} + +template +void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) +{} + template::value, int> = 0> auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( - arr.reserve(std::declval()), j.template get(), void()) { using std::end; ConstructibleArrayType ret; - ret.reserve(j.size()); + from_json_array_reserve(ret, j.size(), priority_tag<1> {}); std::transform(j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { @@ -310,27 +289,6 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p arr = std::move(ret); } -template::value, - int> = 0> -inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, - priority_tag<0> /*unused*/) -{ - using std::end; - - ConstructibleArrayType ret; - std::transform( - j.begin(), j.end(), std::inserter(ret, end(ret)), - [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); - arr = std::move(ret); -} - template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < is_constructible_array_type::value&& @@ -433,9 +391,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } // overload for arithmetic types, not chosen for basic_json template arguments -// (BooleanType, etc.); note: Is it really necessary to provide explicit -// overloads for boolean_t etc. in case of a custom BooleanType which is not -// an arithmetic type? +// (BooleanType, etc.) template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& @@ -531,7 +487,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, p template std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(cxpr_and>, is_compatible_reference_type>...>::value, + static_assert(conjunction>, is_compatible_reference_type>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } @@ -554,10 +510,10 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); } -template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, - typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> -inline void from_json(const BasicJsonType& j, std::map& m) +// shared body for std::map/std::unordered_map with a non-string Key: both +// containers are read from an array of [key, value] pairs the same way +template +void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -570,33 +526,29 @@ inline void from_json(const BasicJsonType& j, std::map(), p.at(1).template get()); + m.emplace(p.at(0).template get(), p.at(1).template get()); } } +template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::map& m) +{ + from_json_pair_array_to_map(j, m); +} + template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -inline void from_json(const BasicJsonType& j, std::unordered_map& m) +void from_json(const BasicJsonType& j, std::unordered_map& m) { - if (JSON_HEDLEY_UNLIKELY(!j.is_array())) - { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); - } - m.clear(); - for (const auto& p : j) - { - if (JSON_HEDLEY_UNLIKELY(!p.is_array())) - { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); - } - m.emplace(p.at(0).template get(), p.at(1).template get()); - } + from_json_pair_array_to_map(j, m); } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM -// Workaround for MSVC 19.51 (and possibly later): in large in large cpp files, the compiler may fail to resolve with generic has_from_json (issue #4996) +// Workaround for MSVC 19.51 (and possibly later): in large cpp files, the compiler may fail to resolve with generic has_from_json (issue #4996) template struct has_from_json : std::true_type {}; diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 7b97068f3..2dba6c163 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -178,7 +178,7 @@ struct external_constructor template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < !std::is_same::value -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION && !is_compatible_range_view::value #endif , int > = 0 > @@ -222,9 +222,7 @@ struct external_constructor j.assert_invariant(); } - // std::ranges does not work properly on MinGW due to incomplete C++20 support - // see https://github.com/nlohmann/json/issues/4916 -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION template>::value, int> = 0> static void construct(BasicJsonType& j, CompatibleArrayType && arr) @@ -384,7 +382,7 @@ template < typename BasicJsonType, typename CompatibleArrayType, !std::is_same::value&& !is_compatible_binary_type::value&& !is_basic_json::value -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION && !is_compatible_range_view::value #endif , @@ -394,7 +392,7 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) external_constructor::construct(j, arr); } -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION template < typename BasicJsonType, typename T, enable_if_t < is_compatible_range_view>::value && !is_compatible_string_type>::value diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index 808a3b6bf..155f4a4e5 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -286,6 +286,27 @@ class other_error : public exception other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; +/*! +@brief helper function to call JSON_THROW from a template +@note JSON_THROW is a macro that, depending on the JSON_THROW_USER / + JSON_TRY_USER / JSON_NOEXCEPTION configuration, may expand to code + that does not reference its argument (e.g. `std::abort()`), which + would trigger a compilation error if the argument's type depends on + a template parameter that is otherwise unused. Wrapping the call in + a templated function avoids this and gives the compiler a single + place to see the (possibly unused) parameter. +*/ +template +void templated_json_throw(ExceptionType exception) +{ + JSON_THROW(exception); + + // JSON_THROW may expand to code that discards its argument (e.g. when + // exceptions are disabled) - the cast below avoids an unused-parameter + // warning with -Werror in that case + (void)exception; +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index 2115b6aeb..84fdd07f8 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -60,9 +60,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); // superficial check for the LegacyBidirectionalIterator named requirement - static_assert(std::is_base_of::value - && std::is_base_of::iterator_category>::value, - "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); + // note: only array_t::iterator is checked here; object_t::iterator may be + // a forward-only iterator as long as reverse iteration and operator-- + // are never used on it + static_assert(std::is_base_of::iterator_category>::value, + "basic_json iterator assumes array type iterators satisfy the LegacyBidirectionalIterator named requirement."); public: /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index a10f4f39f..788af1047 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -240,6 +240,72 @@ class json_pointer } private: + /*! + @brief result of @ref parse_array_index + + @ref array_index maps each value to the corresponding parse_error/out_of_range + exception; @ref contains and @ref get_checked_or_null, which must not throw for + an out-of-range or unrepresentable index, switch on it directly instead. + */ + enum class array_index_status + { + ok, ///< @a s is a valid, representable array index + leading_zero, ///< @a s begins with '0' but has more than one character + not_a_number, ///< @a s does not begin with a digit + unresolved, ///< @a s could not be converted to an integer + exceeds_size_type ///< @a s converts to an integer that exceeds size_type + }; + + /*! + @param[in] s reference token to be converted into an array index + @param[out] idx the integer representation of @a s if @ref array_index_status::ok + is returned; left unchanged otherwise + + @return whether @a s is a valid array index, and if not, why + + @note this function never throws; @ref array_index and the callers that must not + throw (@ref contains, @ref get_checked_or_null) build on it instead of each + re-implementing the RFC 6901 digit rules and the @a size_type range check + */ + template + static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept + { + using size_type = typename BasicJsonType::size_type; + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + return array_index_status::leading_zero; + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + { + return array_index_status::not_a_number; + } + + const char* p = s.data(); + char* p_end = nullptr; // NOLINT(misc-const-correctness) + errno = 0; // strtoull doesn't reset errno + const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) + if (p == p_end // invalid input or empty string + || errno == ERANGE // out of range + || JSON_HEDLEY_UNLIKELY(static_cast(p_end - p) != s.size())) // incomplete read + { + return array_index_status::unresolved; + } + + // the index does not fit into size_type; on 64-bit platforms this is + // only SIZE_MAX itself (see #2203 and #5395) + if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) + { + return array_index_status::exceeds_size_type; + } + + idx = static_cast(res); + return array_index_status::ok; + } + /*! @param[in] s reference token to be converted into an array index @@ -253,39 +319,23 @@ class json_pointer template static typename BasicJsonType::size_type array_index(const string_t& s) { - using size_type = typename BasicJsonType::size_type; - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + typename BasicJsonType::size_type idx{}; + switch (parse_array_index(s, idx)) { - JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + case array_index_status::leading_zero: + JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + case array_index_status::not_a_number: + JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + case array_index_status::unresolved: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + case array_index_status::exceeds_size_type: + JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); + case array_index_status::ok: + default: + break; } - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) - { - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); - } - - const char* p = s.data(); - char* p_end = nullptr; // NOLINT(misc-const-correctness) - errno = 0; // strtoull doesn't reset errno - const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) - if (p == p_end // invalid input or empty string - || errno == ERANGE // out of range - || JSON_HEDLEY_UNLIKELY(static_cast(p_end - p) != s.size())) // incomplete read - { - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); - } - - // the index does not fit into size_type; on 64-bit platforms this is - // only SIZE_MAX itself (see #2203 and #5395) - if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) - { - JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); - } - - return static_cast(res); + return idx; } JSON_PRIVATE_UNLESS_TESTED: @@ -590,63 +640,6 @@ class json_pointer return *ptr; } - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - template - const BasicJsonType& get_checked(const BasicJsonType* ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->type()) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, detail::concat( - "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), - ") is out of range"), ptr)); - } - - const auto idx = array_index(reference_token); - // Bounds check before access to avoid exception with JSON_NOEXCEPTION - if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) - { - JSON_THROW(detail::out_of_range::create(401, detail::concat( - "array index ", std::to_string(idx), " is out of range"), ptr)); - } - ptr = &ptr->operator[](idx); - break; - } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); - } - } - - return *ptr; - } - /*! @brief return a pointer to the pointed to value, or `nullptr` if the pointer cannot be resolved because a key is missing, an array @@ -685,29 +678,24 @@ class json_pointer return nullptr; } - // tokens that array_index() rejects with parse_error.106/109 - // are passed on to it; all other tokens that it would reject - // with out_of_range.404/410 are detected here, so that this - // also works without exceptions - if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 && !(reference_token[0] >= '1' && reference_token[0] <= '9'))) + // a malformed index throws parse_error.106/109; an + // index that is syntactically valid but cannot be + // represented (out_of_range.404/410) is treated like an + // out-of-range index below + typename BasicJsonType::size_type idx{}; + switch (parse_array_index(reference_token, idx)) { - static_cast(array_index(reference_token)); // throws parse_error.106/109 + case array_index_status::leading_zero: + JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); + case array_index_status::not_a_number: + JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); + case array_index_status::unresolved: + case array_index_status::exceeds_size_type: + return nullptr; + case array_index_status::ok: + default: + break; } - if (JSON_HEDLEY_UNLIKELY(reference_token.empty() || !std::all_of(reference_token.begin(), reference_token.end(), [](const char c) - { - return c >= '0' && c <= '9'; - }))) - { - return nullptr; - } - errno = 0; // strtoull() does not reset errno on success - char* p_end = nullptr; // NOLINT(misc-const-correctness) - const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int) - if (JSON_HEDLEY_UNLIKELY(errno == ERANGE || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) - { - return nullptr; - } - const auto idx = static_cast(magnitude); if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { @@ -734,8 +722,8 @@ class json_pointer } /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number + @note unlike array_index(), this never throws: a malformed or unrepresentable + array index reference token is treated like a missing key (see #5395) */ template bool contains(const BasicJsonType* ptr) const @@ -763,49 +751,17 @@ class json_pointer // "-" always fails the range check return false; } - if (JSON_HEDLEY_UNLIKELY(reference_token.empty())) - { - // an empty reference token is not an array index; array_index() - // would throw out_of_range.404 -- contains() must not throw (see #5395) - return false; - } - if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !('0' <= reference_token[0] && reference_token[0] <= '9'))) - { - // invalid char - return false; - } - if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) - { - if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) - { - // the first char should be between '1' and '9' - return false; - } - for (std::size_t i = 1; i < reference_token.size(); i++) - { - if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) - { - // other char should be between '0' and '9' - return false; - } - } - } - // the reference token consists only of digits at this point (cf. checks - // above); however, its numeric value might not be representable, in which - // case array_index() would throw out_of_range.404/410 -- contains() must - // not throw (see #5395), so such a reference token is treated as "not found" - errno = 0; // strtoull() does not reset errno on success - char* p_end = nullptr; // NOLINT(misc-const-correctness) - const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int) - if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX - || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) + // any parse failure (malformed index, or one that is syntactically + // valid but not representable as size_type) means the reference + // token cannot denote an existing array element -- contains() must + // not throw (see #5395), so it is treated as "not found" + typename BasicJsonType::size_type idx{}; + if (JSON_HEDLEY_UNLIKELY(parse_array_index(reference_token, idx) != array_index_status::ok)) { - // the array index cannot be represented as size_type return false; } - const auto idx = array_index(reference_token); if (idx >= ptr->size()) { // index out of range diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 3a5f7e606..17118927c 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -9,7 +9,6 @@ #pragma once #include // declval, pair -#include #include // This file contains all internal macro definitions (except those affecting ABI) @@ -140,10 +139,12 @@ // libstdc++ < 11 has incomplete C++20 ranges (issue #4440) #elif defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11 #define JSON_HAS_RANGES 0 - // libc++ < 16 has incomplete C++20 ranges (issue #4440) + // clang < 16 with libstdc++ does not implement the ranges customization + // points libstdc++ declares, so its C++20 ranges support is incomplete (issue #5161) #elif defined(__clang__) && !defined(__apple_build_version__) \ && __clang_major__ < 16 && defined(__GLIBCXX__) #define JSON_HAS_RANGES 0 + // libc++ < 16 has incomplete C++20 ranges (issue #4440) #elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000 #define JSON_HAS_RANGES 0 // nvcc CUDA 12.0/12.1 chokes on the enable_borrowed_range variable-template @@ -158,6 +159,18 @@ #endif #endif +// std::ranges view conversion (to_json/is_compatible_array_type_impl) additionally +// needs to be disabled on MinGW, whose std::ranges support is incomplete +// (issue #4916); this macro combines both conditions so the check and its +// reason are not duplicated at every use site. +#ifndef JSON_HAS_RANGE_VIEW_CONVERSION + #if JSON_HAS_RANGES && !defined(__MINGW32__) + #define JSON_HAS_RANGE_VIEW_CONVERSION 1 + #else + #define JSON_HAS_RANGE_VIEW_CONVERSION 0 + #endif +#endif + #ifndef JSON_HAS_STD_FORMAT #if defined(JSON_HAS_CPP_20) && defined(__cpp_lib_format) #define JSON_HAS_STD_FORMAT 1 @@ -279,21 +292,6 @@ -/*! -@brief function to wrap JSON_THROW_MACRO - there can be compilation errors about - there being no arguments to JSON_THROW that depend on template arguments - if this is not used to call JSON_THROW -*/ -template -void templated_json_throw(ExceptionType exception) -{ - JSON_THROW(exception); - - /* JSON_THROW(exception) discards exception and aborts - void cast needed to supress - compilation error if compiled with -Werror and Wunused-parameter */ - (void)exception; -} - /*! @brief macro to briefly define a mapping between an enum and JSON with exception on invalid input @@ -314,7 +312,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -329,7 +327,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -874,30 +872,6 @@ void templated_json_throw(ExceptionType exception) \ template \ using result_of_##std_name = decltype(std_name(std::declval()...)); \ - } \ - \ - namespace detail2 { \ - struct std_name##_tag \ - { \ - }; \ - \ - template \ - std_name##_tag std_name(T&&...); \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - \ - template \ - struct would_call_std_##std_name \ - { \ - static constexpr auto const value = ::nlohmann::detail:: \ - is_detected_exact::value; \ - }; \ - } /* namespace detail2 */ \ - \ - template \ - struct would_call_std_##std_name : detail2::would_call_std_##std_name \ - { \ } #ifndef JSON_USE_IMPLICIT_CONVERSIONS diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index 1e6e6cce6..8e1d49842 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -35,6 +35,7 @@ #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON #undef JSON_HAS_RANGES + #undef JSON_HAS_RANGE_VIEW_CONVERSION #undef JSON_HAS_STD_FORMAT #undef JSON_HAS_STATIC_RTTI #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON diff --git a/include/nlohmann/detail/meta/call_std/begin.hpp b/include/nlohmann/detail/meta/call_std/begin.hpp index a086a5f3d..d12bbf7da 100644 --- a/include/nlohmann/detail/meta/call_std/begin.hpp +++ b/include/nlohmann/detail/meta/call_std/begin.hpp @@ -12,6 +12,6 @@ NLOHMANN_JSON_NAMESPACE_BEGIN -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin) NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/meta/call_std/end.hpp b/include/nlohmann/detail/meta/call_std/end.hpp index 40c9942b9..dc1895fc4 100644 --- a/include/nlohmann/detail/meta/call_std/end.hpp +++ b/include/nlohmann/detail/meta/call_std/end.hpp @@ -12,6 +12,6 @@ NLOHMANN_JSON_NAMESPACE_BEGIN -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end) NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/meta/is_sax.hpp b/include/nlohmann/detail/meta/is_sax.hpp index 8e8a0de24..ac238b0c2 100644 --- a/include/nlohmann/detail/meta/is_sax.hpp +++ b/include/nlohmann/detail/meta/is_sax.hpp @@ -8,7 +8,7 @@ #pragma once -#include // size_t +#include // size_t #include // declval #include // string @@ -70,37 +70,6 @@ using parse_error_function_t = decltype(std::declval().parse_error( std::declval(), std::declval(), std::declval())); -template -struct is_sax -{ - private: - static_assert(is_basic_json::value, - "BasicJsonType must be of type basic_json<...>"); - - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using exception_t = typename BasicJsonType::exception; - - public: - static constexpr bool value = - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value; -}; - template struct is_sax_static_asserts { @@ -120,8 +89,6 @@ struct is_sax_static_asserts "Missing/invalid function: bool null()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool boolean(bool)"); static_assert( is_detected_exact::value, diff --git a/include/nlohmann/detail/meta/logic.hpp b/include/nlohmann/detail/meta/logic.hpp deleted file mode 100644 index cb50a5d19..000000000 --- a/include/nlohmann/detail/meta/logic.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -#ifdef JSON_HAS_CPP_17 - -template -struct cxpr_or_impl : std::integral_constant < bool, (Booleans || ...) > {}; - -template -struct cxpr_and_impl : std::integral_constant < bool, (Booleans &&...) > {}; - -#else - -template -struct cxpr_or_impl : std::false_type {}; - -template -struct cxpr_or_impl : std::true_type {}; - -template -struct cxpr_or_impl : cxpr_or_impl {}; - -template -struct cxpr_and_impl : std::true_type {}; - -template -struct cxpr_and_impl : cxpr_and_impl {}; - -template -struct cxpr_and_impl : std::false_type {}; - -#endif - -template -struct cxpr_not : std::integral_constant < bool, !Boolean::value > {}; - -template -struct cxpr_or : cxpr_or_impl {}; - -template -struct cxpr_or_c : cxpr_or_impl {}; - -template -struct cxpr_and : cxpr_and_impl {}; - -template -struct cxpr_and_c : cxpr_and_impl {}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 14029c0fc..68bf4ad2c 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -314,6 +314,13 @@ template struct conjunction : std::conditional(B::value), conjunction, B>::type {}; +// https://en.cppreference.com/w/cpp/types/disjunction +template struct disjunction : std::false_type { }; +template struct disjunction : B { }; +template +struct disjunction +: std::conditional(B::value), B, disjunction>::type {}; + // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; @@ -508,9 +515,7 @@ template struct is_range_view_optional_type> : std: template struct is_range_view_optional_type : std::false_type {}; #endif -// std::ranges does not work properly on MinGW due to incomplete C++20 support -// see https://github.com/nlohmann/json/issues/4916 -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION // SafeToCheck guards against types that trigger circular constraints when // std::ranges::view is evaluated on GCC 12 / libstdc++ 12: @@ -549,7 +554,7 @@ struct is_compatible_array_type_impl < // filter_view) can match BOTH this iterator-based specialization AND the view-based one // below, causing ambiguity. Exclude views here so the two specializations are mutually // exclusive: this one handles plain iterable containers, the other handles views. -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION && !is_compatible_range_view::value #endif >> @@ -559,7 +564,7 @@ struct is_compatible_array_type_impl < range_value_t>::value; }; -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, @@ -635,7 +640,6 @@ struct is_compatible_integer_type_impl < std::is_integral::value&& !std::is_same::value >> { - // is there an assert somewhere on overflows? using RealLimits = std::numeric_limits; using CompatibleLimits = std::numeric_limits; @@ -863,20 +867,7 @@ struct has_capacity : std::integral_constant -struct is_ordered_map -{ - using one = char; - - struct two - { - char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - }; - - template static one test( decltype(&C::capacity) ) ; - template static two test(...); - - enum { value = sizeof(test(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-use-enum-class) -}; +struct is_ordered_map : has_capacity {}; // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > @@ -900,10 +891,8 @@ using all_signed = conjunction...>; template using all_unsigned = conjunction...>; -// there's a disjunction trait in another PR; replace when merged template -using same_sign = std::integral_constant < bool, - all_signed::value || all_unsigned::value >; +using same_sign = disjunction, all_unsigned>; template using never_out_of_range = std::integral_constant < bool, diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a91bd9361..f8fc9fdb9 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -276,104 +276,6 @@ #include // declval, pair -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template struct make_void -{ - using type = void; -}; -template using void_t = typename make_void::type; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -// https://en.cppreference.com/w/cpp/experimental/is_detected -struct nonesuch -{ - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(nonesuch const&) = delete; - nonesuch(nonesuch const&&) = delete; - void operator=(nonesuch const&) = delete; - void operator=(nonesuch&&) = delete; -}; - -template class Op, - class... Args> -struct detector -{ - using value_t = std::false_type; - using type = Default; -}; - -template class Op, class... Args> -struct detector>, Op, Args...> -{ - using value_t = std::true_type; - using type = Op; -}; - -template class Op, class... Args> -using is_detected = typename detector::value_t; - -template class Op, class... Args> -struct is_detected_lazy : is_detected { }; - -template class Op, class... Args> -using detected_t = typename detector::type; - -template class Op, class... Args> -using detected_or = detector; - -template class Op, class... Args> -using detected_or_t = typename detected_or::type; - -template class Op, class... Args> -using is_detected_exact = std::is_same>; - -template class Op, class... Args> -using is_detected_convertible = - std::is_convertible, To>; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - // #include @@ -2552,10 +2454,12 @@ JSON_HEDLEY_DIAGNOSTIC_POP // libstdc++ < 11 has incomplete C++20 ranges (issue #4440) #elif defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11 #define JSON_HAS_RANGES 0 - // libc++ < 16 has incomplete C++20 ranges (issue #4440) + // clang < 16 with libstdc++ does not implement the ranges customization + // points libstdc++ declares, so its C++20 ranges support is incomplete (issue #5161) #elif defined(__clang__) && !defined(__apple_build_version__) \ && __clang_major__ < 16 && defined(__GLIBCXX__) #define JSON_HAS_RANGES 0 + // libc++ < 16 has incomplete C++20 ranges (issue #4440) #elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000 #define JSON_HAS_RANGES 0 // nvcc CUDA 12.0/12.1 chokes on the enable_borrowed_range variable-template @@ -2570,6 +2474,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP #endif #endif +// std::ranges view conversion (to_json/is_compatible_array_type_impl) additionally +// needs to be disabled on MinGW, whose std::ranges support is incomplete +// (issue #4916); this macro combines both conditions so the check and its +// reason are not duplicated at every use site. +#ifndef JSON_HAS_RANGE_VIEW_CONVERSION + #if JSON_HAS_RANGES && !defined(__MINGW32__) + #define JSON_HAS_RANGE_VIEW_CONVERSION 1 + #else + #define JSON_HAS_RANGE_VIEW_CONVERSION 0 + #endif +#endif + #ifndef JSON_HAS_STD_FORMAT #if defined(JSON_HAS_CPP_20) && defined(__cpp_lib_format) #define JSON_HAS_STD_FORMAT 1 @@ -2691,21 +2607,6 @@ JSON_HEDLEY_DIAGNOSTIC_POP -/*! -@brief function to wrap JSON_THROW_MACRO - there can be compilation errors about - there being no arguments to JSON_THROW that depend on template arguments - if this is not used to call JSON_THROW -*/ -template -void templated_json_throw(ExceptionType exception) -{ - JSON_THROW(exception); - - /* JSON_THROW(exception) discards exception and aborts - void cast needed to supress - compilation error if compiled with -Werror and Wunused-parameter */ - (void)exception; -} - /*! @brief macro to briefly define a mapping between an enum and JSON with exception on invalid input @@ -2726,7 +2627,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -2741,7 +2642,7 @@ void templated_json_throw(ExceptionType exception) return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -3286,30 +3187,6 @@ void templated_json_throw(ExceptionType exception) \ template \ using result_of_##std_name = decltype(std_name(std::declval()...)); \ - } \ - \ - namespace detail2 { \ - struct std_name##_tag \ - { \ - }; \ - \ - template \ - std_name##_tag std_name(T&&...); \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - \ - template \ - struct would_call_std_##std_name \ - { \ - static constexpr auto const value = ::nlohmann::detail:: \ - is_detected_exact::value; \ - }; \ - } /* namespace detail2 */ \ - \ - template \ - struct would_call_std_##std_name : detail2::would_call_std_##std_name \ - { \ } #ifndef JSON_USE_IMPLICIT_CONVERSIONS @@ -3851,6 +3728,31 @@ NLOHMANN_JSON_NAMESPACE_END // #include // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END // #include @@ -3922,7 +3824,7 @@ NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_BEGIN -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin) NLOHMANN_JSON_NAMESPACE_END @@ -3942,13 +3844,84 @@ NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_BEGIN -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end) NLOHMANN_JSON_NAMESPACE_END // #include // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ @@ -4314,6 +4287,13 @@ template struct conjunction : std::conditional(B::value), conjunction, B>::type {}; +// https://en.cppreference.com/w/cpp/types/disjunction +template struct disjunction : std::false_type { }; +template struct disjunction : B { }; +template +struct disjunction +: std::conditional(B::value), B, disjunction>::type {}; + // https://en.cppreference.com/w/cpp/types/negation template struct negation : std::integral_constant < bool, !B::value > { }; @@ -4508,9 +4488,7 @@ template struct is_range_view_optional_type> : std: template struct is_range_view_optional_type : std::false_type {}; #endif -// std::ranges does not work properly on MinGW due to incomplete C++20 support -// see https://github.com/nlohmann/json/issues/4916 -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION // SafeToCheck guards against types that trigger circular constraints when // std::ranges::view is evaluated on GCC 12 / libstdc++ 12: @@ -4549,7 +4527,7 @@ struct is_compatible_array_type_impl < // filter_view) can match BOTH this iterator-based specialization AND the view-based one // below, causing ambiguity. Exclude views here so the two specializations are mutually // exclusive: this one handles plain iterable containers, the other handles views. -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION && !is_compatible_range_view::value #endif >> @@ -4559,7 +4537,7 @@ struct is_compatible_array_type_impl < range_value_t>::value; }; -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, @@ -4635,7 +4613,6 @@ struct is_compatible_integer_type_impl < std::is_integral::value&& !std::is_same::value >> { - // is there an assert somewhere on overflows? using RealLimits = std::numeric_limits; using CompatibleLimits = std::numeric_limits; @@ -4863,20 +4840,7 @@ struct has_capacity : std::integral_constant -struct is_ordered_map -{ - using one = char; - - struct two - { - char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - }; - - template static one test( decltype(&C::capacity) ) ; - template static two test(...); - - enum { value = sizeof(test(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-use-enum-class) -}; +struct is_ordered_map : has_capacity {}; // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > @@ -4900,10 +4864,8 @@ using all_signed = conjunction...>; template using all_unsigned = conjunction...>; -// there's a disjunction trait in another PR; replace when merged template -using same_sign = std::integral_constant < bool, - all_signed::value || all_unsigned::value >; +using same_sign = disjunction, all_unsigned>; template using never_out_of_range = std::integral_constant < bool, @@ -5452,6 +5414,27 @@ class other_error : public exception other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; +/*! +@brief helper function to call JSON_THROW from a template +@note JSON_THROW is a macro that, depending on the JSON_THROW_USER / + JSON_TRY_USER / JSON_NOEXCEPTION configuration, may expand to code + that does not reference its argument (e.g. `std::abort()`), which + would trigger a compilation error if the argument's type depends on + a template parameter that is otherwise unused. Wrapping the call in + a templated function avoids this and gives the compiler a single + place to see the (possibly unused) parameter. +*/ +template +void templated_json_throw(ExceptionType exception) +{ + JSON_THROW(exception); + + // JSON_THROW may expand to code that discards its argument (e.g. when + // exceptions are disabled) - the cast below avoids an unused-parameter + // warning with -Werror in that case + (void)exception; +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -5521,63 +5504,6 @@ NLOHMANN_JSON_NAMESPACE_END // #include -// #include - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -#ifdef JSON_HAS_CPP_17 - -template -struct cxpr_or_impl : std::integral_constant < bool, (Booleans || ...) > {}; - -template -struct cxpr_and_impl : std::integral_constant < bool, (Booleans &&...) > {}; - -#else - -template -struct cxpr_or_impl : std::false_type {}; - -template -struct cxpr_or_impl : std::true_type {}; - -template -struct cxpr_or_impl : cxpr_or_impl {}; - -template -struct cxpr_and_impl : std::true_type {}; - -template -struct cxpr_and_impl : cxpr_and_impl {}; - -template -struct cxpr_and_impl : std::false_type {}; - -#endif - -template -struct cxpr_not : std::integral_constant < bool, !Boolean::value > {}; - -template -struct cxpr_or : cxpr_or_impl {}; - -template -struct cxpr_or_c : cxpr_or_impl {}; - -template -struct cxpr_and : cxpr_and_impl {}; - -template -struct cxpr_and_c : cxpr_and_impl {}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - // #include // #include @@ -5763,62 +5689,29 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) }); } +// element is not itself a C array: read it directly +template +auto from_json_c_array_element(const BasicJsonType& j, T& e) +-> decltype(e = j.template get(), void()) +{ + e = j.template get(); +} + +// element is itself a C array: recurse one dimension at a time, so any rank is supported template -auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) +void from_json_c_array_element(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { for (std::size_t i = 0; i < N; ++i) { - arr[i] = j.at(i).template get(); + from_json_c_array_element(j.at(i), arr[i]); } } -template -auto from_json(const BasicJsonType& j, T (&arr)[N1][N2]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) +template +auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get::type>(), void()) { - for (std::size_t i1 = 0; i1 < N1; ++i1) - { - for (std::size_t i2 = 0; i2 < N2; ++i2) - { - arr[i1][i2] = j.at(i1).at(i2).template get(); - } - } -} - -template -auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) -{ - for (std::size_t i1 = 0; i1 < N1; ++i1) - { - for (std::size_t i2 = 0; i2 < N2; ++i2) - { - for (std::size_t i3 = 0; i3 < N3; ++i3) - { - arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get(); - } - } - } -} - -template -auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) --> decltype(j.template get(), void()) -{ - for (std::size_t i1 = 0; i1 < N1; ++i1) - { - for (std::size_t i2 = 0; i2 < N2; ++i2) - { - for (std::size_t i3 = 0; i3 < N3; ++i3) - { - for (std::size_t i4 = 0; i4 < N4; ++i4) - { - arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get(); - } - } - } - } + from_json_c_array_element(j, arr); } template @@ -5838,20 +5731,33 @@ auto from_json_array_impl(const BasicJsonType& j, std::array& arr, } } +// reserve() is called through this pair (modeled on from_json_object_reserve) +// so from_json_array_impl below has a single body for both ConstructibleArrayType +// that support reserve() and those that don't. +template +auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/) +-> decltype(arr.reserve(size), void()) +{ + arr.reserve(size); +} + +template +void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) +{} + template::value, int> = 0> auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( - arr.reserve(std::declval()), j.template get(), void()) { using std::end; ConstructibleArrayType ret; - ret.reserve(j.size()); + from_json_array_reserve(ret, j.size(), priority_tag<1> {}); std::transform(j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { @@ -5862,27 +5768,6 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p arr = std::move(ret); } -template::value, - int> = 0> -inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, - priority_tag<0> /*unused*/) -{ - using std::end; - - ConstructibleArrayType ret; - std::transform( - j.begin(), j.end(), std::inserter(ret, end(ret)), - [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); - arr = std::move(ret); -} - template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < is_constructible_array_type::value&& @@ -5985,9 +5870,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } // overload for arithmetic types, not chosen for basic_json template arguments -// (BooleanType, etc.); note: Is it really necessary to provide explicit -// overloads for boolean_t etc. in case of a custom BooleanType which is not -// an arithmetic type? +// (BooleanType, etc.) template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& @@ -6083,7 +5966,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, p template std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(cxpr_and>, is_compatible_reference_type>...>::value, + static_assert(conjunction>, is_compatible_reference_type>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } @@ -6106,10 +5989,10 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); } -template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, - typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> -inline void from_json(const BasicJsonType& j, std::map& m) +// shared body for std::map/std::unordered_map with a non-string Key: both +// containers are read from an array of [key, value] pairs the same way +template +void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -6122,33 +6005,29 @@ inline void from_json(const BasicJsonType& j, std::map(), p.at(1).template get()); + m.emplace(p.at(0).template get(), p.at(1).template get()); } } +template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::map& m) +{ + from_json_pair_array_to_map(j, m); +} + template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -inline void from_json(const BasicJsonType& j, std::unordered_map& m) +void from_json(const BasicJsonType& j, std::unordered_map& m) { - if (JSON_HEDLEY_UNLIKELY(!j.is_array())) - { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); - } - m.clear(); - for (const auto& p : j) - { - if (JSON_HEDLEY_UNLIKELY(!p.is_array())) - { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); - } - m.emplace(p.at(0).template get(), p.at(1).template get()); - } + from_json_pair_array_to_map(j, m); } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM -// Workaround for MSVC 19.51 (and possibly later): in large in large cpp files, the compiler may fail to resolve with generic has_from_json (issue #4996) +// Workaround for MSVC 19.51 (and possibly later): in large cpp files, the compiler may fail to resolve with generic has_from_json (issue #4996) template struct has_from_json : std::true_type {}; @@ -6844,7 +6723,7 @@ struct external_constructor template < typename BasicJsonType, typename CompatibleArrayType, enable_if_t < !std::is_same::value -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION && !is_compatible_range_view::value #endif , int > = 0 > @@ -6888,9 +6767,7 @@ struct external_constructor j.assert_invariant(); } - // std::ranges does not work properly on MinGW due to incomplete C++20 support - // see https://github.com/nlohmann/json/issues/4916 -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION template>::value, int> = 0> static void construct(BasicJsonType& j, CompatibleArrayType && arr) @@ -7050,7 +6927,7 @@ template < typename BasicJsonType, typename CompatibleArrayType, !std::is_same::value&& !is_compatible_binary_type::value&& !is_basic_json::value -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION && !is_compatible_range_view::value #endif , @@ -7060,7 +6937,7 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) external_constructor::construct(j, arr); } -#if JSON_HAS_RANGES && !defined(__MINGW32__) +#if JSON_HAS_RANGE_VIEW_CONVERSION template < typename BasicJsonType, typename T, enable_if_t < is_compatible_range_view>::value && !is_compatible_string_type>::value @@ -13449,7 +13326,7 @@ NLOHMANN_JSON_NAMESPACE_END -#include // size_t +#include // size_t #include // declval #include // string @@ -13514,37 +13391,6 @@ using parse_error_function_t = decltype(std::declval().parse_error( std::declval(), std::declval(), std::declval())); -template -struct is_sax -{ - private: - static_assert(is_basic_json::value, - "BasicJsonType must be of type basic_json<...>"); - - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using exception_t = typename BasicJsonType::exception; - - public: - static constexpr bool value = - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value; -}; - template struct is_sax_static_asserts { @@ -13564,8 +13410,6 @@ struct is_sax_static_asserts "Missing/invalid function: bool null()"); static_assert(is_detected_exact::value, "Missing/invalid function: bool boolean(bool)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool boolean(bool)"); static_assert( is_detected_exact::value, @@ -18725,9 +18569,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); // superficial check for the LegacyBidirectionalIterator named requirement - static_assert(std::is_base_of::value - && std::is_base_of::iterator_category>::value, - "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); + // note: only array_t::iterator is checked here; object_t::iterator may be + // a forward-only iterator as long as reverse iteration and operator-- + // are never used on it + static_assert(std::is_base_of::iterator_category>::value, + "basic_json iterator assumes array type iterators satisfy the LegacyBidirectionalIterator named requirement."); public: /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. @@ -19869,6 +19715,72 @@ class json_pointer } private: + /*! + @brief result of @ref parse_array_index + + @ref array_index maps each value to the corresponding parse_error/out_of_range + exception; @ref contains and @ref get_checked_or_null, which must not throw for + an out-of-range or unrepresentable index, switch on it directly instead. + */ + enum class array_index_status + { + ok, ///< @a s is a valid, representable array index + leading_zero, ///< @a s begins with '0' but has more than one character + not_a_number, ///< @a s does not begin with a digit + unresolved, ///< @a s could not be converted to an integer + exceeds_size_type ///< @a s converts to an integer that exceeds size_type + }; + + /*! + @param[in] s reference token to be converted into an array index + @param[out] idx the integer representation of @a s if @ref array_index_status::ok + is returned; left unchanged otherwise + + @return whether @a s is a valid array index, and if not, why + + @note this function never throws; @ref array_index and the callers that must not + throw (@ref contains, @ref get_checked_or_null) build on it instead of each + re-implementing the RFC 6901 digit rules and the @a size_type range check + */ + template + static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept + { + using size_type = typename BasicJsonType::size_type; + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + return array_index_status::leading_zero; + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + { + return array_index_status::not_a_number; + } + + const char* p = s.data(); + char* p_end = nullptr; // NOLINT(misc-const-correctness) + errno = 0; // strtoull doesn't reset errno + const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) + if (p == p_end // invalid input or empty string + || errno == ERANGE // out of range + || JSON_HEDLEY_UNLIKELY(static_cast(p_end - p) != s.size())) // incomplete read + { + return array_index_status::unresolved; + } + + // the index does not fit into size_type; on 64-bit platforms this is + // only SIZE_MAX itself (see #2203 and #5395) + if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) + { + return array_index_status::exceeds_size_type; + } + + idx = static_cast(res); + return array_index_status::ok; + } + /*! @param[in] s reference token to be converted into an array index @@ -19882,39 +19794,23 @@ class json_pointer template static typename BasicJsonType::size_type array_index(const string_t& s) { - using size_type = typename BasicJsonType::size_type; - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + typename BasicJsonType::size_type idx{}; + switch (parse_array_index(s, idx)) { - JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + case array_index_status::leading_zero: + JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + case array_index_status::not_a_number: + JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + case array_index_status::unresolved: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + case array_index_status::exceeds_size_type: + JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); + case array_index_status::ok: + default: + break; } - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) - { - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); - } - - const char* p = s.data(); - char* p_end = nullptr; // NOLINT(misc-const-correctness) - errno = 0; // strtoull doesn't reset errno - const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) - if (p == p_end // invalid input or empty string - || errno == ERANGE // out of range - || JSON_HEDLEY_UNLIKELY(static_cast(p_end - p) != s.size())) // incomplete read - { - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); - } - - // the index does not fit into size_type; on 64-bit platforms this is - // only SIZE_MAX itself (see #2203 and #5395) - if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) - { - JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); - } - - return static_cast(res); + return idx; } JSON_PRIVATE_UNLESS_TESTED: @@ -20219,63 +20115,6 @@ class json_pointer return *ptr; } - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - template - const BasicJsonType& get_checked(const BasicJsonType* ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->type()) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, detail::concat( - "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), - ") is out of range"), ptr)); - } - - const auto idx = array_index(reference_token); - // Bounds check before access to avoid exception with JSON_NOEXCEPTION - if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) - { - JSON_THROW(detail::out_of_range::create(401, detail::concat( - "array index ", std::to_string(idx), " is out of range"), ptr)); - } - ptr = &ptr->operator[](idx); - break; - } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); - } - } - - return *ptr; - } - /*! @brief return a pointer to the pointed to value, or `nullptr` if the pointer cannot be resolved because a key is missing, an array @@ -20314,29 +20153,24 @@ class json_pointer return nullptr; } - // tokens that array_index() rejects with parse_error.106/109 - // are passed on to it; all other tokens that it would reject - // with out_of_range.404/410 are detected here, so that this - // also works without exceptions - if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 && !(reference_token[0] >= '1' && reference_token[0] <= '9'))) + // a malformed index throws parse_error.106/109; an + // index that is syntactically valid but cannot be + // represented (out_of_range.404/410) is treated like an + // out-of-range index below + typename BasicJsonType::size_type idx{}; + switch (parse_array_index(reference_token, idx)) { - static_cast(array_index(reference_token)); // throws parse_error.106/109 + case array_index_status::leading_zero: + JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); + case array_index_status::not_a_number: + JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); + case array_index_status::unresolved: + case array_index_status::exceeds_size_type: + return nullptr; + case array_index_status::ok: + default: + break; } - if (JSON_HEDLEY_UNLIKELY(reference_token.empty() || !std::all_of(reference_token.begin(), reference_token.end(), [](const char c) - { - return c >= '0' && c <= '9'; - }))) - { - return nullptr; - } - errno = 0; // strtoull() does not reset errno on success - char* p_end = nullptr; // NOLINT(misc-const-correctness) - const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int) - if (JSON_HEDLEY_UNLIKELY(errno == ERANGE || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) - { - return nullptr; - } - const auto idx = static_cast(magnitude); if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { @@ -20363,8 +20197,8 @@ class json_pointer } /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number + @note unlike array_index(), this never throws: a malformed or unrepresentable + array index reference token is treated like a missing key (see #5395) */ template bool contains(const BasicJsonType* ptr) const @@ -20392,49 +20226,17 @@ class json_pointer // "-" always fails the range check return false; } - if (JSON_HEDLEY_UNLIKELY(reference_token.empty())) - { - // an empty reference token is not an array index; array_index() - // would throw out_of_range.404 -- contains() must not throw (see #5395) - return false; - } - if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !('0' <= reference_token[0] && reference_token[0] <= '9'))) - { - // invalid char - return false; - } - if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) - { - if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) - { - // the first char should be between '1' and '9' - return false; - } - for (std::size_t i = 1; i < reference_token.size(); i++) - { - if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) - { - // other char should be between '0' and '9' - return false; - } - } - } - // the reference token consists only of digits at this point (cf. checks - // above); however, its numeric value might not be representable, in which - // case array_index() would throw out_of_range.404/410 -- contains() must - // not throw (see #5395), so such a reference token is treated as "not found" - errno = 0; // strtoull() does not reset errno on success - char* p_end = nullptr; // NOLINT(misc-const-correctness) - const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int) - if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX - || magnitude >= static_cast((std::numeric_limits::max)()))) // NOLINT(runtime/int) + // any parse failure (malformed index, or one that is syntactically + // valid but not representable as size_type) means the reference + // token cannot denote an existing array element -- contains() must + // not throw (see #5395), so it is treated as "not found" + typename BasicJsonType::size_type idx{}; + if (JSON_HEDLEY_UNLIKELY(parse_array_index(reference_token, idx) != array_index_status::ok)) { - // the array index cannot be represented as size_type return false; } - const auto idx = array_index(reference_token); if (idx >= ptr->size()) { // index out of range @@ -34096,6 +33898,7 @@ struct formatter // NOLINT(cert-dcl58-c #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON #undef JSON_HAS_RANGES + #undef JSON_HAS_RANGE_VIEW_CONVERSION #undef JSON_HAS_STD_FORMAT #undef JSON_HAS_STATIC_RTTI #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index df8685529..5f83a9253 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -430,6 +430,37 @@ TEST_CASE("value conversion") CHECK(std::equal(std::begin(nbs[0][0][0]), std::end(nbs[1][1][1]), std::begin(nbs2[0][0][0]))); } + SECTION("built-in arrays: 5D") + { + // NOLINTBEGIN(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + const int nbs[1][1][1][2][2] = {{{{{0, 1}, {2, 3}}}}}; + int nbs2[1][1][1][2][2] = {{{{{0, 0}, {0, 0}}}}}; + // NOLINTEND(misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + + const json j2 = nbs; + j2.get_to(nbs2); + CHECK(std::equal(std::begin(nbs[0][0][0][0]), std::end(nbs[0][0][0][1]), std::begin(nbs2[0][0][0][0]))); + } + + SECTION("built-in arrays: mismatched shape") + { + // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + int nbs2[2][3] = {{0, 0, 0}, {0, 0, 0}}; + // NOLINTEND(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + + SECTION("not an array") + { + const json j2 = 42; + CHECK_THROWS_WITH_AS(j2.get_to(nbs2), "[json.exception.type_error.304] cannot use at() with number", json::type_error&); + } + + SECTION("too few elements") + { + const json j2 = {{0, 1, 2}}; + CHECK_THROWS_WITH_AS(j2.get_to(nbs2), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&); + } + } + SECTION("std::deque") { std::deque a{"previous", "value"}; @@ -1748,6 +1779,34 @@ NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState, {STRICT_TS_COMPLETED, "completed"}, }) +// regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on +// unqualified lookup of a helper name that a user's own namespace may also declare +namespace ns_with_colliding_name +{ +// NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name +inline void templated_json_throw(int /*unused*/) {} + +enum class colliding_enum { a, b }; + +// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(colliding_enum, +{ + {colliding_enum::a, "a"}, + {colliding_enum::b, "b"} +}) +} // namespace ns_with_colliding_name + +TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name") +{ + using ns_with_colliding_name::colliding_enum; + + CHECK(json(colliding_enum::a) == "a"); + CHECK(colliding_enum::b == json("b")); + + json _; + CHECK_THROWS_WITH_AS(_ = json("nope").get(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&); +} + TEST_CASE("Strict JSON to enum mapping") { SECTION("enum class") diff --git a/tests/src/unit-type_traits.cpp b/tests/src/unit-type_traits.cpp index 6dc166d03..a3937bf2e 100644 --- a/tests/src/unit-type_traits.cpp +++ b/tests/src/unit-type_traits.cpp @@ -10,10 +10,14 @@ #if JSON_TEST_USING_MULTIPLE_HEADERS #include + #include #else #include #endif +#include +#include + TEST_CASE("type traits") { SECTION("is_c_string") @@ -83,4 +87,12 @@ TEST_CASE("type traits") } } } + + SECTION("is_ordered_map") + { + using nlohmann::detail::is_ordered_map; + + CHECK(is_ordered_map>::value); + CHECK_FALSE(is_ordered_map>::value); + } } From a0b71e272006c400703daeb2f4dc3248e47ddb01 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:57:41 +0200 Subject: [PATCH 20/27] Deduplicate basic_json internals; make insert(pos, json&&) move (#5727) * Remove unused private aliases from basic_json The private aliases primitive_iterator_t, internal_iterator and output_adapter_t are not used anywhere: iter_impl, binary_writer and the tests refer to the detail:: names directly. As the aliases are private, no user or derived class can depend on them. The internal_iterator.hpp include stays because iter_impl needs it. Part of #5724 Signed-off-by: Niels Lohmann * Fix meta()'s dead, syntactically invalid HP aCC branch The HP aCC branch of basic_json::meta() was missing a semicolon and has therefore never compiled; adding only a semicolon would also make it throw type_error.305, since it assigned a plain string to result["compiler"] and then indexed into it like the other branches do into an object. Make the branch consistent with the others by assigning an object with "family" and "version" keys, narrow the condition to __HP_aCC (a C compiler cannot build this header-only library), and fix meta.md, which documented the old (impossible) plain-string behavior. Part of #5724 Signed-off-by: Niels Lohmann * Stop the noexcept null constructor delegating to a throwing one basic_json(std::nullptr_t) delegated to basic_json(value_t), whose underlying json_value(value_t) constructor allocates for other types and can therefore throw, which is why the noexcept had a NOLINT(bugprone-exception-escape). The delegated-to constructor also called assert_invariant() a second time. The default member initializers of data already produce the same null state (a value-initialized, i.e. zeroed, union with object == nullptr), so the delegation and its NOLINT can simply be dropped. Part of #5724 Signed-off-by: Niels Lohmann * Remove four cppcheck accessForwarded suppressions in the move constructor basic_json(basic_json&&) built its base subobject with std::forward(other), so cppcheck saw the whole of other as forwarded and flagged every subsequent access to it as accessForwarded, three of them still marked "TODO check". Only the base subobject is actually moved from; cast explicitly to the base type instead, the way ordered_map already does, so cppcheck can tell the two are unrelated. Behavior is unchanged: for a non-reference T, std::forward(x) is defined as static_cast(x). Part of #5724 Signed-off-by: Niels Lohmann * Remove stale cppcheck suppressions and name the local parser in parse() Running the pinned cppcheck (ci_cppcheck's invocation) without --inline-suppr across all configurations reports no syntaxError, no ignoredReturnValue and no assertWithSideEffect, so the corresponding suppressions in json_fwd.hpp, string_concat.hpp and assert_invariant() no longer match anything (json_fwd.hpp's is kept, since downstream users who run an older cppcheck against it could still hit the warning it once silenced). The three basic_json::parse() overloads still trigger a false-positive accessMoved/accessForwarded because they build a temporary parser and call .parse() on it in the same expression; giving that parser a name makes the warning go away without changing behavior, and removes the last of the inline suppressions on these functions. Part of #5724 Signed-off-by: Niels Lohmann * Deduplicate the string/binary cleanup in the two erase() overloads erase(pos) and erase(first, last) each carried a byte-identical 14-line block that destroys and deallocates a string or binary value before resetting the type to null. That reimplements the string/binary cases of json_value::destroy(), so any future change to how those values are freed would have to be made in three places instead of one. Both overloads now just call destroy() and reset the union; for the other primitive types (boolean, numbers) destroy() is a no-op, so behavior is unchanged. Also fix erase(first, last)'s error-path branch hint, which used JSON_HEDLEY_LIKELY where erase(pos), the iterator-range constructor, and every other error path in the class use JSON_HEDLEY_UNLIKELY. This only affects code layout, not semantics. Part of #5724 Signed-off-by: Niels Lohmann * Fix stale and copy-pasted comments in basic_json Several comments no longer match the code: the class invariant and assert_invariant()'s doc still named the members m_value/m_type (now m_data.m_value/m_data.m_type) and did not mention the binary invariant that assert_invariant() already checks; the json_value note and the get() @tparam list omitted binary_t even though binary is a variable-length, pointer-stored type like the others; the key-based value() overload's brief said "via JSON Pointer", which is the other overload; and swap(binary_t&)/swap(binary_t::container_type&) both carried "swap only works for strings", copied from swap(string_t&). Comment-only change; behavior, the public API and the ABI are unchanged. The private get_impl() doxygen and the emplace() comments that border #5585's hunk are intentionally left alone. Part of #5724 Signed-off-by: Niels Lohmann * Deduplicate the 16 copied from_cbor/msgpack/ubjson/bjdata/bon8/bson bodies Each of the 16 binary deserialization overloads (from_cbor, from_msgpack, from_ubjson, from_bjdata, from_bon8, from_bson, each in an InputType&& and an iterator/sentinel version, plus the deprecated span overloads of from_cbor/from_msgpack/from_ubjson/from_bson) had the same body, differing only in the input_format_t value. Every copy built a temporary binary_reader from std::move(ia) and called sax_parse on it in the same expression, which also produced a false-positive cppcheck accessMoved on all 16 lines and needed a NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) on the four span overloads. Add a private from_binary_impl() helper that builds the reader as a named local instead, and make each of the 16 overloads a one-line forward to it. All public signatures, default arguments, JSON_HEDLEY_WARN_UNUSED_RESULT and JSON_HEDLEY_DEPRECATED_FOR attributes are unchanged, tag_handler keeps defaulting to cbor_tag_handler_t::error for the non-CBOR formats (matching binary_reader::sax_parse's own default), and the helper is placed in the existing private section before the binary section banner rather than between the from_* overloads, so from_binary_impl() itself does not collide with #5688's insertion point. Collapsing the from_bjdata/from_bon8 bodies into one-line forwards does rewrite the "return result; }" context lines that #5688 inserts its two deprecated overloads after, so that PR will need a small manual rebase (reinserting its overloads after the new one-line bodies) rather than applying cleanly. Part of #5724 Signed-off-by: Niels Lohmann * Make insert(pos, basic_json&&) move its argument instead of copying it insert(const_iterator pos, basic_json&& val) delegated to insert(pos, val), but val is a named rvalue reference, so inside the function it is an lvalue: the call always resolved to insert(const_iterator, const basic_json&) and deep-copied the value. This has been the case since the overload was introduced, in every release. push_back(basic_json&&), by contrast, already moves. Give the rvalue overload its own body with the same two checks (type_error.309, invalid_iterator.202), then move the argument into a local before inserting it. Moving into a local first, rather than inserting std::move(val) directly, keeps this safe even when val aliases an element of the same array (e.g. arr.insert(arr.begin(), std::move(arr[1]))), since std::vector::insert(pos, T&&) is not guaranteed to handle an argument that aliases one of its own elements. This is a deliberate, small behavior change: the moved-from argument now ends up null afterwards, the same as after push_back(&&), instead of keeping its old value unchanged. No signature changes, so the public API and ABI are unaffected. Add unit-modifiers coverage for the moved-from state and for self-aliasing insertion, both with and without reallocation of the underlying array. Part of #5724 Signed-off-by: Niels Lohmann * Deduplicate object key lookup and checked at() access at()/find()/count()/contains()/const operator[]/erase_internal() each repeated the raw object lookup (m_value.object->find(key)), and the six at() overloads additionally repeated the type_error.304 check and out_of_range.401/403 throw. Route them all through two new private helpers, object_lookup()/object_at() (plus array_at() for the index overloads of at()), templated on the constness of the receiver so one body serves both the const and non-const overload. count() is left untouched, since it already goes through object_t::count() rather than a second find(). The at(KeyType&&) overloads used to forward the same key twice: once into object->find() and again, on the not-found path, into the string_t() conversion for the exception message. object_at() now forwards it only into the lookup and reuses the (unmoved) key for the message. clang-tidy 22 (Docker silkeh/clang:22) still flags that reuse under bugprone-use-after-move/hicpp-invalid-access-moved even with the single forward, since it cannot see that object_t::find() (a plain std::map or ordered_map) never actually moves from its argument; add a NOLINTNEXTLINE with that reasoning rather than avoid the pattern. No signature, exception id/message, or set_parent() behavior changes. Overlaps #5689, #5705, #5606, #5687 and #5585, which touch the same hunks; whichever of this commit and those PRs lands second will need a small rebase. Signed-off-by: Niels Lohmann * Deduplicate the lookup/default/throw body of value() The six non-deprecated value() overloads each held a full copy of the same body: the four key-based overloads looked up the key and either returned the found element converted to the requested type or the default value (throwing type_error.306 if this is not an object), and the two json_pointer overloads did the same via ptr.get_checked_or_null(), throwing type_error.306 unless is_structured(). Replace the duplicated bodies with two private helpers, value_member() and value_pointee(), that return a const basic_json* (null when not found) and do the type check/throw once each. Every value() overload now just picks between the found pointer's get() and the default. Same signatures, template parameters, SFINAE conditions, exception id, message and this context on every overload. Overlaps #5689 (routes find() through lookup_key()) and #5705 (adds a deleted integral-key value() next to these overloads); whichever of this commit and those PRs lands second will need a small rebase. #5724 item 4 Signed-off-by: Niels Lohmann * Deduplicate the null-to-container conversion into convert_null_to() Nine sites wrote out the same "turn a null value into an empty array or object" logic with two different idioms: operator[](size_type), operator[](key_type), operator[](KeyType&&) and update() set m_type then assigned m_value.array/object directly via create(), while the three push_back() overloads, emplace_back() and emplace() set m_type then assigned m_value = value_t::array/object (going through json_value's converting constructor and a temporary). Both idioms end up calling create() and produce the same state, just via a different path; both also share a latent exception-safety bug, since m_type is written before the (possibly throwing) allocation, so a throwing allocator leaves m_type == array/object with a null pointer behind it, violating the class invariant and crashing on the next access to, or destruction of, the value. Add a private convert_null_to(value_t) helper and call it from all nine sites. Unlike the idioms it replaces, it allocates the container first and only then writes m_type, so a throwing allocation leaves the value as a valid null instead of a mistyped, half-constructed one; verified with a throwing allocator (see unit-allocator.cpp's bad_allocator) that j["x"] = ... on a null j now stays null, and no longer trips assert_invariant()/crashes, when create() throws. Same allocator usage and assert_invariant() call as before, otherwise. Overlaps #5585, which reorders these same nine blocks for exception safety; whichever of this commit and that PR lands second will need a small rebase. Signed-off-by: Niels Lohmann * Deduplicate the linear key search in ordered_map emplace, at, erase(key), count and find each repeated the same "for (auto it = begin(); it != end(); ++it) if (m_compare(it->first, key)) ..." loop (15 copies across their key_type and transparent KeyType&& overloads), and both erase(key) overloads additionally repeated the exception-sensitive in-place reconstruction (destroy, placement-new, pop_back) used to remove an element while keeping the const Key non-movable. Add two private helpers: find_impl(Self&, KeyType&&), a static member template that runs the search once for either constness of the receiver, and erase_at(iterator), which keeps the existing pop_back-based reconstruction instead of switching to erase()/resize() (which would add a DefaultInsertable requirement). Route find, at, count, emplace, insert(const value_type&) and both erase(key) overloads through them. Same signatures, is_usable_as_key_type constraints and exception messages/types. Overlaps #5609 and #5685, which both rewrite emplace (#5609 also touches insert and adds private members at the end of the class); whichever of this commit and those PRs lands second will need a rebase. #5724 item 6 Signed-off-by: Niels Lohmann * Re-enable bugprone-use-after-move/hicpp-invalid-access-moved These two checks (and portability-template-virtual-member-function) were disabled in #4489 (November 2024) "only removed to get the CI going". portability-template-virtual-member-function is a separate, still-open cleanup (#5725 item 3 on its own branch) and stays disabled here; this commit only re-enables the move/forward checks and cleans up what they flag on this branch. The move constructor (json.hpp) already casts to the base type instead of forwarding the whole object (#5724 item 9), so it no longer trips either check. The at(KeyType&&) double-forward this check used to flag was reduced to a single forward with the now-unforwarded reuse annotated by a NOLINTNEXTLINE in #5724 item 3's object_at() helper (clang-tidy 22 still flags that reuse even after a single forward; see that commit's message). What is left here: - from_json_inplace_array_impl(), from_json_tuple_impl_base() and the std::pair overload of from_json_tuple_impl() forwarded j into every j.at(...) call in a pack expansion or a pair of calls. at() has no ref-qualified overloads, so the forward was a no-op; call j.at(...) directly. - container_input_adapter_factory::create() forwards container twice on purpose, into begin() and end(), so both see the same value category and produce matching iterator types. Annotate it with NOLINTNEXTLINE and a comment instead of changing it. - unit-class_parser.cpp's "move constructor resets the moved-from value to npos" test still pointed at the pre-static_cast move constructor by line number and mentioned the cppcheck-suppress annotation that #5724 item 9 already removed; update the comment. No behavior change anywhere in include/. Verified with clang-tidy 22.1.8 (Docker silkeh/clang:22, --platform linux/amd64) against a TU including json.hpp with the repo's .clang-tidy: bugprone-use-after-move and hicpp-invalid-access-moved report nothing unsuppressed. Overlaps #5737 (open PR for the rest of #5725 item 3: the from_json.hpp/input_adapters.hpp cleanup above, and portability-template-virtual-member-function), which currently keeps both checks disabled pending this move-constructor change; whichever of this commit and that PR lands second will need a small rebase of .clang-tidy. Signed-off-by: Niels Lohmann * Make convert_null_to() take the container type as a template argument Passing array_t or object_t instead of a value_t makes an invalid target a compile error instead of a runtime assertion, and removes the branch. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .clang-tidy | 9 - docs/mkdocs/docs/api/basic_json/meta.md | 2 +- .../nlohmann/detail/input/input_adapters.hpp | 2 +- include/nlohmann/detail/string_concat.hpp | 1 - include/nlohmann/json.hpp | 644 +++++++----------- include/nlohmann/ordered_map.hpp | 183 ++--- tests/src/unit-class_parser.cpp | 8 +- tests/src/unit-modifiers.cpp | 43 ++ 8 files changed, 366 insertions(+), 526 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index fa3e03ae3..a16136785 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,18 +1,9 @@ -# bugprone-use-after-move (hicpp-invalid-access-moved is its alias) still flags -# the basic_json move constructor, which forwards the whole object to its base -# class (#5724), and two forwards in the error-message construction of -# at(KeyType&&) (json.hpp, both overloads: find(std::forward(key)) -# followed by string_t(std::forward(key)) in the throw), which #5689 -# rewrites. Re-enable both checks once those changes have landed. # portability-avoid-pragma-once: kept disabled on purpose. #pragma once is accepted # by every supported compiler, and tools/amalgamate/amalgamate.py strips it from # single_include, so there is nothing left to fix here. Checks: '*, - -bugprone-use-after-move, - -hicpp-invalid-access-moved, - -altera-id-dependent-backward-branch, -altera-struct-pack-align, -altera-unroll-loops, diff --git a/docs/mkdocs/docs/api/basic_json/meta.md b/docs/mkdocs/docs/api/basic_json/meta.md index 55476c5f3..9b1ac2a4e 100644 --- a/docs/mkdocs/docs/api/basic_json/meta.md +++ b/docs/mkdocs/docs/api/basic_json/meta.md @@ -13,7 +13,7 @@ JSON object holding version information | key | description | |-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). On HP aCC compilers, `compiler` is instead the plain string `hp`. | +| `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `hp`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). | | `copyright` | The copyright line for the library as string. | | `name` | The name of the library as string. | | `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. | diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 09f0ded14..7f2f79cbf 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -746,7 +746,7 @@ struct container_input_adapter_factory< ContainerType, { // container is forwarded twice on purpose: the resulting begin/end // iterator types must match adapter_type, computed the same way - // NOLINTNEXTLINE(bugprone-use-after-move) + // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) return input_adapter(begin(std::forward(container)), end(std::forward(container))); } }; diff --git a/include/nlohmann/detail/string_concat.hpp b/include/nlohmann/detail/string_concat.hpp index a39dd5e63..74002c878 100644 --- a/include/nlohmann/detail/string_concat.hpp +++ b/include/nlohmann/detail/string_concat.hpp @@ -39,7 +39,6 @@ inline std::size_t concat_length(const char /*c*/, const Args& ... rest) template inline std::size_t concat_length(const char* cstr, const Args& ... rest) { - // cppcheck-suppress ignoredReturnValue return ::strlen(cstr) + concat_length(rest...); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 87e1dbc04..89ba13233 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -115,11 +115,12 @@ struct is_std_optional> : std::true_type {}; @brief a class to store JSON values @internal -@invariant The member variables @a m_value and @a m_type have the following -relationship: -- If `m_type == value_t::object`, then `m_value.object != nullptr`. -- If `m_type == value_t::array`, then `m_value.array != nullptr`. -- If `m_type == value_t::string`, then `m_value.string != nullptr`. +@invariant The member variables @a m_data.m_value and @a m_data.m_type have +the following relationship: +- If `m_data.m_type == value_t::object`, then `m_data.m_value.object != nullptr`. +- If `m_data.m_type == value_t::array`, then `m_data.m_value.array != nullptr`. +- If `m_data.m_type == value_t::string`, then `m_data.m_value.string != nullptr`. +- If `m_data.m_type == value_t::binary`, then `m_data.m_value.binary != nullptr`. The invariants are checked by member function assert_invariant(). @note ObjectType trick from https://stackoverflow.com/a/9860911 @@ -182,18 +183,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } private: - using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; - template - using internal_iterator = ::nlohmann::detail::internal_iterator; template using iter_impl = ::nlohmann::detail::iter_impl; template using iteration_proxy = ::nlohmann::detail::iteration_proxy; template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; - template - using output_adapter_t = ::nlohmann::detail::output_adapter_t; - template using binary_reader = ::nlohmann::detail::binary_reader; template using binary_writer = ::nlohmann::detail::binary_writer; @@ -334,8 +329,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec std::to_string(__GNUC_PATCHLEVEL__)) } }; -#elif defined(__HP_cc) || defined(__HP_aCC) - result["compiler"] = "hp" +#elif defined(__HP_aCC) + result["compiler"] = {{"family", "hp"}, {"version", __HP_aCC}}; #elif defined(__IBMCPP__) result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; #elif defined(_MSC_VER) @@ -477,9 +472,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary | binary | pointer to @ref binary_t null | null | *no value is stored* - @note Variable-length types (objects, arrays, and strings) are stored as - pointers. The size of the union should not exceed 64 bits if the default - value types are used. + @note Variable-length types (objects, arrays, strings, and binary + values) are stored as pointers. The size of the union should not exceed + 64 bits if the default value types are used. @since version 1.0.0 */ @@ -731,7 +726,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec end of every constructor to make sure that created objects respect the invariant. Furthermore, it has to be called each time the type of a JSON value is changed, because the invariant expresses a relationship between - @a m_type and @a m_value. + @a m_data.m_type and @a m_data.m_value. Furthermore, the parent relation is checked for arrays and objects: If @a check_parents true and the value is an array or object, then the @@ -752,7 +747,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #if JSON_DIAGNOSTICS JSON_TRY { - // cppcheck-suppress assertWithSideEffect JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j) { return j.m_parent == this; @@ -1924,8 +1918,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief create a null object /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ - basic_json(std::nullptr_t = nullptr) noexcept // NOLINT(bugprone-exception-escape) - : basic_json(value_t::null) + basic_json(std::nullptr_t = nullptr) noexcept { assert_invariant(); } @@ -2289,15 +2282,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief move constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept - : json_base_class_t(std::forward(other)), - m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check + : json_base_class_t(std::move(static_cast(other))), + m_data(std::move(other.m_data)) #if JSON_DIAGNOSTIC_POSITIONS - , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check - , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check + , start_position(other.start_position) + , end_position(other.end_position) #endif { // check that the passed value is valid - other.assert_invariant(false); // cppcheck-suppress[accessForwarded] + other.assert_invariant(false); // invalidate payload other.m_data.m_type = value_t::null; @@ -2864,7 +2857,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. + @ref number_unsigned_t, @ref number_float_t, or @ref binary_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -3032,6 +3025,90 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} + private: + /// @brief look up @a key in the object held by @a j, for either constness of @a j + /// @note the single place that performs a (possibly transparent) object key lookup + template + static auto object_lookup(Self& j, KeyType&& key) + -> decltype(j.m_data.m_value.object->find(lookup_key(std::forward(key)))) + { + return j.m_data.m_value.object->find(lookup_key(std::forward(key))); + } + + /// @brief checked object element access used by the at() overloads taking a key + /// @throw type_error.304 if @a j is not an object + /// @throw out_of_range.403 if @a key is not found + template + static auto object_at(Self& j, KeyType&& key) + -> decltype((object_lookup(j, std::forward(key))->second)) + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + } + + auto it = object_lookup(j, std::forward(key)); + if (it == j.m_data.m_value.object->end()) + { + // key is only forwarded into the lookup above: object_t::find() (a plain + // std::map or ordered_map) never moves from its argument, so key is still + // valid here regardless of whether KeyType was deduced as an rvalue reference + // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) + JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(key), "' not found"), &j)); + } + return it->second; + } + + /// @brief checked array element access used by the at() overloads taking an index + /// @throw type_error.304 if @a j is not an array + /// @throw out_of_range.401 if @a idx is out of range + template + static auto array_at(Self& j, size_type idx) + -> decltype((*j.m_data.m_value.array)[idx]) + { + // at only works for arrays + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + } + + if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) + { + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + } + + return (*j.m_data.m_value.array)[idx]; + } + + /// @brief convert a null value to an empty container of type @a Container + /// @tparam Container array_t or object_t; any other type does not compile + template + void convert_null_to() + { + JSON_ASSERT(is_null()); + // create the container before touching the type, so a throwing + // allocation leaves this value as a valid null rather than a type + // tag with a dangling/null pointer behind it + set_container(create()); + assert_invariant(); + } + + /// @brief store a freshly created array and set the matching type + void set_container(array_t* array) noexcept + { + m_data.m_value.array = array; + m_data.m_type = value_t::array; + } + + /// @brief store a freshly created object and set the matching type + void set_container(object_t* object) noexcept + { + m_data.m_value.object = object; + m_data.m_type = value_t::object; + } + + public: //////////////////// // element access // //////////////////// @@ -3044,54 +3121,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/at/ reference at(size_type idx) { - // at only works for arrays - if (JSON_HEDLEY_UNLIKELY(!is_array())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - if (JSON_HEDLEY_UNLIKELY(idx >= m_data.m_value.array->size())) - { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } - - return set_parent((*m_data.m_value.array)[idx]); + return set_parent(array_at(*this, idx)); } /// @brief access specified array element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ const_reference at(size_type idx) const { - // at only works for arrays - if (JSON_HEDLEY_UNLIKELY(!is_array())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - if (JSON_HEDLEY_UNLIKELY(idx >= m_data.m_value.array->size())) - { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } - - return (*m_data.m_value.array)[idx]; + return array_at(*this, idx); } /// @brief access specified object element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ reference at(const typename object_t::key_type& key) { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(key); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); - } - return set_parent(it->second); + return set_parent(object_at(*this, key)); } /// @brief access specified object element with bounds checking @@ -3100,36 +3144,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_usable_as_basic_json_key_type::value, int> = 0> reference at(KeyType && key) { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); - } - return set_parent(it->second); + return set_parent(object_at(*this, std::forward(key))); } /// @brief access specified object element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ const_reference at(const typename object_t::key_type& key) const { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(key); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); - } - return it->second; + return object_at(*this, key); } /// @brief access specified object element with bounds checking @@ -3138,18 +3160,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_usable_as_basic_json_key_type::value, int> = 0> const_reference at(KeyType && key) const { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); - } - return it->second; + return object_at(*this, std::forward(key)); } /// @brief access specified array element @@ -3159,9 +3170,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value.array = create(); - assert_invariant(); + convert_null_to(); } // operator[] only works for arrays @@ -3227,9 +3236,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value.object = create(); - assert_invariant(); + convert_null_to(); } // operator[] only works for objects @@ -3249,7 +3256,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto it = m_data.m_value.object->find(key); + auto it = object_lookup(*this, key); JSON_ASSERT(it != m_data.m_value.object->end()); return it->second; } @@ -3280,9 +3287,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value.object = create(); - assert_invariant(); + convert_null_to(); } // operator[] only works for objects @@ -3304,7 +3309,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); + auto it = object_lookup(*this, std::forward(key)); JSON_ASSERT(it != m_data.m_value.object->end()); return it->second; } @@ -3324,6 +3329,36 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_c_string_uncvref::value, string_t, typename std::decay::type >; + /// @brief look up @a key for value(), the single place shared by all key-based overloads + /// @throw type_error.306 if this is not an object + /// @return a pointer to the found value, or `nullptr` if @a key was not found + template + const basic_json* value_member(KeyType&& key) const + { + // value only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + const auto it = find(std::forward(key)); + return it != end() ? &*it : nullptr; + } + + /// @brief resolve @a ptr for value(), the single place shared by both json_pointer overloads + /// @throw type_error.306 if this is not an array or object + /// @return a pointer to the resolved value, or `nullptr` if @a ptr does not resolve + const basic_json* value_pointee(const json_pointer& ptr) const + { + // value only works for arrays and objects + if (JSON_HEDLEY_UNLIKELY(!is_structured())) + { + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + return ptr.get_checked_or_null(this); + } + public: // an integer literal 0 would otherwise convert to a null const char* and from there to key_type template::value, int> = 0> @@ -3337,20 +3372,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(key); - if (it != end()) - { - return it->template get(); - } - - return default_value; - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(key); + return found != nullptr ? found->template get() : default_value; } /// @brief access specified object element with default value @@ -3362,20 +3386,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ReturnType value(const typename object_t::key_type& key, ValueType && default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(key); - if (it != end()) - { - return it->template get(); - } - - return std::forward(default_value); - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(key); + return found != nullptr ? found->template get() : std::forward(default_value); } /// @brief access specified object element with default value @@ -3388,23 +3401,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ValueType value(KeyType && key, const ValueType& default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(std::forward(key)); - if (it != end()) - { - return it->template get(); - } - - return default_value; - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(std::forward(key)); + return found != nullptr ? found->template get() : default_value; } - /// @brief access specified object element via JSON Pointer with default value + /// @brief access specified object element with default value /// @sa https://json.nlohmann.me/api/basic_json/value/ template < class ValueType, class KeyType, class ReturnType = typename value_return_type::type, detail::enable_if_t < @@ -3415,20 +3417,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ReturnType value(KeyType && key, ValueType && default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(std::forward(key)); - if (it != end()) - { - return it->template get(); - } - - return std::forward(default_value); - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(std::forward(key)); + return found != nullptr ? found->template get() : std::forward(default_value); } /// @brief access specified object element via JSON Pointer with default value @@ -3438,21 +3429,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ValueType value(const json_pointer& ptr, const ValueType& default_value) const { - // value only works for arrays and objects - if (JSON_HEDLEY_LIKELY(is_structured())) - { - // If the pointer resolves to a value, return it. Otherwise, return - // 'default_value'. - const auto* res = ptr.get_checked_or_null(this); - if (JSON_HEDLEY_LIKELY(res != nullptr)) - { - return res->template get(); - } - - return default_value; - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. + const auto* found = value_pointee(ptr); + return found != nullptr ? found->template get() : default_value; } /// @brief access specified object element via JSON Pointer with default value @@ -3463,21 +3443,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ReturnType value(const json_pointer& ptr, ValueType && default_value) const { - // value only works for arrays and objects - if (JSON_HEDLEY_LIKELY(is_structured())) - { - // If the pointer resolves to a value, return it. Otherwise, return - // 'default_value'. - const auto* res = ptr.get_checked_or_null(this); - if (JSON_HEDLEY_LIKELY(res != nullptr)) - { - return res->template get(); - } - - return std::forward(default_value); - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. + const auto* found = value_pointee(ptr); + return found != nullptr ? found->template get() : std::forward(default_value); } template < class ValueType, class BasicJsonType, detail::enable_if_t < @@ -3562,21 +3531,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); } - if (is_string()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.string); - std::allocator_traits::deallocate(alloc, m_data.m_value.string, 1); - m_data.m_value.string = nullptr; - } - else if (is_binary()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.binary); - std::allocator_traits::deallocate(alloc, m_data.m_value.binary, 1); - m_data.m_value.binary = nullptr; - } - + m_data.m_value.destroy(m_data.m_type); + m_data.m_value = {}; m_data.m_type = value_t::null; assert_invariant(); break; @@ -3628,27 +3584,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case value_t::string: case value_t::binary: { - if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() - || !last.m_it.primitive_iterator.is_end())) + if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); } - if (is_string()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.string); - std::allocator_traits::deallocate(alloc, m_data.m_value.string, 1); - m_data.m_value.string = nullptr; - } - else if (is_binary()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.binary); - std::allocator_traits::deallocate(alloc, m_data.m_value.binary, 1); - m_data.m_value.binary = nullptr; - } - + m_data.m_value.destroy(m_data.m_type); + m_data.m_value = {}; m_data.m_type = value_t::null; assert_invariant(); break; @@ -3704,7 +3647,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); } - const auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); + const auto it = object_lookup(*this, std::forward(key)); if (it != m_data.m_value.object->end()) { m_data.m_value.object->erase(it); @@ -3781,7 +3724,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(key); + result.m_it.object_iterator = object_lookup(*this, key); } return result; @@ -3795,7 +3738,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(key); + result.m_it.object_iterator = object_lookup(*this, key); } return result; @@ -3811,7 +3754,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); + result.m_it.object_iterator = object_lookup(*this, std::forward(key)); } return result; @@ -3827,7 +3770,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); + result.m_it.object_iterator = object_lookup(*this, std::forward(key)); } return result; @@ -3858,7 +3801,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT bool contains(const typename object_t::key_type& key) const { - return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end(); + return is_object() && object_lookup(*this, key) != m_data.m_value.object->end(); } /// @brief check the existence of an element in a JSON object @@ -3868,7 +3811,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT bool contains(KeyType && key) const { - return is_object() && m_data.m_value.object->find(lookup_key(std::forward(key))) != m_data.m_value.object->end(); + return is_object() && object_lookup(*this, std::forward(key)) != m_data.m_value.object->end(); } /// @brief check the existence of an element in a JSON object given a JSON pointer @@ -4233,9 +4176,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value = value_t::array; - assert_invariant(); + convert_null_to(); } // add the element to the array (move semantics) @@ -4266,9 +4207,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value = value_t::array; - assert_invariant(); + convert_null_to(); } // add the element to the array @@ -4298,9 +4237,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value = value_t::object; - assert_invariant(); + convert_null_to(); } // add the element to the object @@ -4354,9 +4291,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value = value_t::array; - assert_invariant(); + convert_null_to(); } // add the element to the array (perfect forwarding) @@ -4379,9 +4314,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value = value_t::object; - assert_invariant(); + convert_null_to(); } // add the element to the array (perfect forwarding) @@ -4441,7 +4374,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/insert/ iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { - return insert(std::move(pos), val); + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // moving into a local first keeps this safe even if val aliases + // an element of this array + basic_json tmp(std::move(val)); + return insert_iterator(pos, std::move(tmp)); + } + + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); } /// @brief inserts copies of element into array @@ -4617,14 +4565,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// value is an object; called first by both @ref update overloads void prepare_update() { - // implicitly convert a null value to an empty object; create the - // object before setting the type, so a throwing allocation leaves - // this value null + // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_value.object = create(); - m_data.m_type = value_t::object; - assert_invariant(); + convert_null_to(); } if (JSON_HEDLEY_UNLIKELY(!is_object())) @@ -4833,7 +4777,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) { - // swap only works for strings + // swap only works for binary values if (JSON_HEDLEY_LIKELY(is_binary())) { using std::swap; @@ -4849,7 +4793,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) { - // swap only works for strings + // swap only works for binary values if (JSON_HEDLEY_LIKELY(is_binary())) { using std::swap; @@ -5350,7 +5294,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool ignore_trailing_commas = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded] + auto p = parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + p.parse(true, result); return result; } @@ -5367,7 +5312,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool ignore_trailing_commas = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + auto p = parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + p.parse(true, result); return result; } @@ -5380,7 +5326,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool ignore_trailing_commas = false) { basic_json result; - parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + p.parse(true, result); return result; } @@ -5607,6 +5554,31 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } #endif + private: + /*! + @brief shared implementation of the binary from_cbor/from_msgpack/ + from_ubjson/from_bjdata/from_bon8/from_bson overloads + + Building the @ref binary_reader as a named local, rather than as a + temporary that @ref detail::json_sax_dom_parser::parse is called on in + the same expression, avoids a false-positive cppcheck accessMoved + warning in each of the 16 callers. + */ + template + static basic_json from_binary_impl(InputAdapterType ia, const input_format_t format, + const bool strict, const bool allow_exceptions, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + binary_reader reader(std::move(ia), format); + if (!reader.sax_parse(&sdp, strict, tag_handler)) + { + result = value_t::discarded; + } + return result; + } + ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// @@ -5779,14 +5751,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::cbor, strict, allow_exceptions, tag_handler); } /// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5799,14 +5764,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::cbor, strict, allow_exceptions, tag_handler); } template @@ -5827,15 +5785,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, tag_handler); } /// @brief create a JSON value from an input in MessagePack format @@ -5846,14 +5796,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::msgpack, strict, allow_exceptions); } /// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5865,14 +5808,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::msgpack, strict, allow_exceptions); } template @@ -5891,15 +5827,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::msgpack, strict, allow_exceptions); } /// @brief create a JSON value from an input in UBJSON format @@ -5910,14 +5838,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::ubjson, strict, allow_exceptions); } /// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5929,14 +5850,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::ubjson, strict, allow_exceptions); } template @@ -5955,15 +5869,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::ubjson, strict, allow_exceptions); } /// @brief create a JSON value from an input in BJData format @@ -5974,14 +5880,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bjdata, strict, allow_exceptions); } /// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5993,14 +5892,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions); } /// @brief create a JSON value from an input in BON8 format @@ -6011,14 +5903,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bon8, strict, allow_exceptions); } /// @brief create a JSON value from an input in BON8 format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -6030,14 +5915,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bon8, strict, allow_exceptions); } /// @brief create a JSON value from an input in BSON format @@ -6048,14 +5926,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bson, strict, allow_exceptions); } /// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -6067,14 +5938,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bson, strict, allow_exceptions); } template @@ -6093,15 +5957,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::bson, strict, allow_exceptions); } /// @} diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index d1c247483..eb521114e 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -74,16 +74,43 @@ template , return *this; } +private: + /// @brief find the entry for @a key, for either constness of @a self + /// @note the single place that performs the linear key search + template + static auto find_impl(Self& self, KeyType&& key) -> decltype(self.begin()) + { + for (auto it = self.begin(); it != self.end(); ++it) + { + if (self.m_compare(it->first, key)) + { + return it; + } + } + return self.end(); + } + + /// @brief remove the entry @a it points to, preserving order + /// @note keys are not movable, so the tail is destroyed and re-constructed in place + void erase_at(iterator it) + { + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + } + +public: template::value, int> = 0> std::pair emplace(const key_type& key, V && t) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - return {it, false}; - } + return {it, false}; } append(key, std::forward(t)); return {std::prev(this->end()), true}; @@ -94,12 +121,10 @@ template , detail::is_constructible>::value, int> = 0> std::pair emplace(KeyType && key, V && t) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - return {it, false}; - } + return {it, false}; } append(std::forward(key), std::forward(t)); return {std::prev(this->end()), true}; @@ -131,75 +156,55 @@ template , T& at(const key_type& key) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } template::value, int> = 0> T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } const T& at(const key_type& key) const { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } template::value, int> = 0> const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } size_type erase(const key_type& key) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - // Since we cannot move const Keys, re-construct them in place - for (auto next = it; ++next != this->end(); ++it) - { - it->~value_type(); // Destroy but keep allocation - new (&*it) value_type{std::move(*next)}; - } - Container::pop_back(); - return 1; - } + erase_at(it); + return 1; } return 0; } @@ -208,19 +213,11 @@ template , detail::is_usable_as_key_type::value, int> = 0> size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - // Since we cannot move const Keys, re-construct them in place - for (auto next = it; ++next != this->end(); ++it) - { - it->~value_type(); // Destroy but keep allocation - new (&*it) value_type{std::move(*next)}; - } - Container::pop_back(); - return 1; - } + erase_at(it); + return 1; } return 0; } @@ -285,80 +282,38 @@ template , size_type count(const key_type& key) const { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return 1; - } - } - return 0; + return find_impl(*this, key) != this->end() ? 1 : 0; } template::value, int> = 0> size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return 1; - } - } - return 0; + return find_impl(*this, key) != this->end() ? 1 : 0; } iterator find(const key_type& key) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } template::value, int> = 0> iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } const_iterator find(const key_type& key) const { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } template::value, int> = 0> const_iterator find(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } std::pair insert( value_type&& value ) @@ -368,12 +323,10 @@ template , std::pair insert( const value_type& value ) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, value.first); + if (it != this->end()) { - if (m_compare(it->first, value.first)) - { - return {it, false}; - } + return {it, false}; } append(value); return {--this->end(), true}; diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 75f3757e8..0ae5e382b 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -2684,12 +2684,10 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX") SECTION("move constructor resets the moved-from value to npos") { - // basic_json(basic_json&&) (json.hpp, around line 1951) copies + // basic_json(basic_json&&) copies // other's start_position/end_position into *this and then resets - // other's to npos (see the cppcheck-suppress[accessForwarded] - // annotation there, which flags this reset as worth a second - // look). Only the top-level moved-from value is affected; its - // (moved-away) children are gone along with it. + // other's to npos. Only the top-level moved-from value is + // affected; its (moved-away) children are gone along with it. const std::string s = R"({"a":1,"b":[1,2,3]})"; json a = json::parse(s); const auto a_start = a.start_pos(); diff --git a/tests/src/unit-modifiers.cpp b/tests/src/unit-modifiers.cpp index ee81120cf..532283f95 100644 --- a/tests/src/unit-modifiers.cpp +++ b/tests/src/unit-modifiers.cpp @@ -630,6 +630,49 @@ TEST_CASE("modifiers") } } + SECTION("rvalue at position moves rather than copies") + { + // regression test: insert(pos, basic_json&&) used to forward to + // insert(pos, const basic_json&) because the named rvalue + // reference parameter is itself an lvalue, so it always + // deep-copied its argument instead of moving it + json j_big = std::string(1000, 'x'); + const auto* const original_buffer = j_big.get_ref().data(); + + auto it = j_array.insert(j_array.begin(), std::move(j_big)); + CHECK(j_array.size() == 5); + CHECK(*it == json(std::string(1000, 'x'))); + CHECK((*it).get_ref().data() == original_buffer); + + // the moved-from value is null, the same as after push_back(&&) + CHECK(j_big.is_null()); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + } + + SECTION("self-aliasing insertion") + { + SECTION("without reallocation") + { + json j_self = {1, 2, 3, 4}; + j_self.get_ref().reserve(j_self.size() + 1); + + auto it = j_self.insert(j_self.begin(), std::move(j_self[1])); + CHECK(j_self.size() == 5); + CHECK(*it == json(2)); + CHECK(j_self == json({2, 1, nullptr, 3, 4})); + } + + SECTION("with reallocation") + { + json j_self = {1, 2, 3, 4}; + j_self.get_ref().shrink_to_fit(); + + auto it = j_self.insert(j_self.begin(), std::move(j_self[1])); + CHECK(j_self.size() == 5); + CHECK(*it == json(2)); + CHECK(j_self == json({2, 1, nullptr, 3, 4})); + } + } + SECTION("copies at position") { SECTION("insert before begin()") From c5650eaa3c91c3ff145e055c4937ee7411cc03d2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:59:03 +0200 Subject: [PATCH 21/27] Reject malformed UTF-16/UTF-32 units in wide-string input (#5704) The wide-string input adapter (used for std::u16string, std::u32string, std::wstring, and iterators over 2- or 4-byte character types) passed some malformed code units on to the lexer as values that are neither a byte (0x00..0xFF) nor char_traits::eof(). As a result: - A lone UTF-16 surrogate inside true/false/null was accepted if its low byte matched the expected letter, or ended the input silently if it was the last unit. - A high surrogate followed by a unit that is not its low surrogate swallowed that unit; if the swallowed unit was the newline ending a // comment, the comment silently extended over the next line. - Where wint_t is a signed int (macOS, the BSDs), a negative wchar_t collided with char_traits::eof() (ending the input early) or was truncated to its low byte, depending on its value. The UTF-32 helper now converts the code unit to std::uint32_t before the range checks, so a negative unit reaches the same "emit 0xFF" branch already used for code points above U+10FFFF. The UTF-16 helper now peeks at the next unit before consuming it, and emits 0xFF instead of the raw surrogate when no valid pair is found, matching how ill-formed UTF-8 bytes are rejected elsewhere in the lexer. Fixes #5645. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/input_adapters.hpp | 13 +- single_include/nlohmann/json.hpp | 843 +++++++----------- tests/src/unit-wstring.cpp | 60 +- 3 files changed, 393 insertions(+), 523 deletions(-) diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 7f2f79cbf..9578e275a 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -453,8 +453,10 @@ struct wide_string_input_helper } else { - // get the current character - const auto wc = input.get_character(); + // get the current character; converted to an unsigned type so that + // a negative unit (wint_t is signed on some platforms) is not + // mistaken for an ASCII character or for EOF + const auto wc = static_cast(input.get_character()); if (wc <= 0x10FFFF) { @@ -522,9 +524,11 @@ struct wide_string_input_helper bool valid_pair = false; if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty())) { - const auto wc2 = static_cast(input.get_character()); + // only consume the next unit if it completes the pair + const auto wc2 = static_cast(*input.current); if (0xDC00 <= wc2 && wc2 <= 0xDFFF) { + input.get_character(); const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes_filled = 0; encode_utf8(charcode, [&utf8_bytes, &utf8_bytes_filled](std::uint32_t byte) @@ -537,7 +541,8 @@ struct wide_string_input_helper if (!valid_pair) { - utf8_bytes[0] = static_cast::int_type>(wc); + // emit a byte that is never valid UTF-8 (see the UTF-32 case) + utf8_bytes[0] = 0xFF; utf8_bytes_filled = 1; } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f8fc9fdb9..3b74fd67f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5046,7 +5046,6 @@ inline std::size_t concat_length(const char /*c*/, const Args& ... rest) template inline std::size_t concat_length(const char* cstr, const Args& ... rest) { - // cppcheck-suppress ignoredReturnValue return ::strlen(cstr) + concat_length(rest...); } @@ -8025,8 +8024,10 @@ struct wide_string_input_helper } else { - // get the current character - const auto wc = input.get_character(); + // get the current character; converted to an unsigned type so that + // a negative unit (wint_t is signed on some platforms) is not + // mistaken for an ASCII character or for EOF + const auto wc = static_cast(input.get_character()); if (wc <= 0x10FFFF) { @@ -8094,9 +8095,11 @@ struct wide_string_input_helper bool valid_pair = false; if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty())) { - const auto wc2 = static_cast(input.get_character()); + // only consume the next unit if it completes the pair + const auto wc2 = static_cast(*input.current); if (0xDC00 <= wc2 && wc2 <= 0xDFFF) { + input.get_character(); const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes_filled = 0; encode_utf8(charcode, [&utf8_bytes, &utf8_bytes_filled](std::uint32_t byte) @@ -8109,7 +8112,8 @@ struct wide_string_input_helper if (!valid_pair) { - utf8_bytes[0] = static_cast::int_type>(wc); + // emit a byte that is never valid UTF-8 (see the UTF-32 case) + utf8_bytes[0] = 0xFF; utf8_bytes_filled = 1; } } @@ -8318,7 +8322,7 @@ struct container_input_adapter_factory< ContainerType, { // container is forwarded twice on purpose: the resulting begin/end // iterator types must match adapter_type, computed the same way - // NOLINTNEXTLINE(bugprone-use-after-move) + // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) return input_adapter(begin(std::forward(container)), end(std::forward(container))); } }; @@ -26273,16 +26277,43 @@ template , return *this; } +private: + /// @brief find the entry for @a key, for either constness of @a self + /// @note the single place that performs the linear key search + template + static auto find_impl(Self& self, KeyType&& key) -> decltype(self.begin()) + { + for (auto it = self.begin(); it != self.end(); ++it) + { + if (self.m_compare(it->first, key)) + { + return it; + } + } + return self.end(); + } + + /// @brief remove the entry @a it points to, preserving order + /// @note keys are not movable, so the tail is destroyed and re-constructed in place + void erase_at(iterator it) + { + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + } + +public: template::value, int> = 0> std::pair emplace(const key_type& key, V && t) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - return {it, false}; - } + return {it, false}; } append(key, std::forward(t)); return {std::prev(this->end()), true}; @@ -26293,12 +26324,10 @@ template , detail::is_constructible>::value, int> = 0> std::pair emplace(KeyType && key, V && t) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - return {it, false}; - } + return {it, false}; } append(std::forward(key), std::forward(t)); return {std::prev(this->end()), true}; @@ -26330,75 +26359,55 @@ template , T& at(const key_type& key) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } template::value, int> = 0> T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } const T& at(const key_type& key) const { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } template::value, int> = 0> const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it == this->end()) { - if (m_compare(it->first, key)) - { - return it->second; - } + JSON_THROW(std::out_of_range("key not found")); } - - JSON_THROW(std::out_of_range("key not found")); + return it->second; } size_type erase(const key_type& key) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - // Since we cannot move const Keys, re-construct them in place - for (auto next = it; ++next != this->end(); ++it) - { - it->~value_type(); // Destroy but keep allocation - new (&*it) value_type{std::move(*next)}; - } - Container::pop_back(); - return 1; - } + erase_at(it); + return 1; } return 0; } @@ -26407,19 +26416,11 @@ template , detail::is_usable_as_key_type::value, int> = 0> size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, key); + if (it != this->end()) { - if (m_compare(it->first, key)) - { - // Since we cannot move const Keys, re-construct them in place - for (auto next = it; ++next != this->end(); ++it) - { - it->~value_type(); // Destroy but keep allocation - new (&*it) value_type{std::move(*next)}; - } - Container::pop_back(); - return 1; - } + erase_at(it); + return 1; } return 0; } @@ -26484,80 +26485,38 @@ template , size_type count(const key_type& key) const { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return 1; - } - } - return 0; + return find_impl(*this, key) != this->end() ? 1 : 0; } template::value, int> = 0> size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return 1; - } - } - return 0; + return find_impl(*this, key) != this->end() ? 1 : 0; } iterator find(const key_type& key) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } template::value, int> = 0> iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } const_iterator find(const key_type& key) const { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } template::value, int> = 0> const_iterator find(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (m_compare(it->first, key)) - { - return it; - } - } - return Container::end(); + return find_impl(*this, key); } std::pair insert( value_type&& value ) @@ -26567,12 +26526,10 @@ template , std::pair insert( const value_type& value ) { - for (auto it = this->begin(); it != this->end(); ++it) + const auto it = find_impl(*this, value.first); + if (it != this->end()) { - if (m_compare(it->first, value.first)) - { - return {it, false}; - } + return {it, false}; } append(value); return {--this->end(), true}; @@ -26695,11 +26652,12 @@ struct is_std_optional> : std::true_type {}; @brief a class to store JSON values @internal -@invariant The member variables @a m_value and @a m_type have the following -relationship: -- If `m_type == value_t::object`, then `m_value.object != nullptr`. -- If `m_type == value_t::array`, then `m_value.array != nullptr`. -- If `m_type == value_t::string`, then `m_value.string != nullptr`. +@invariant The member variables @a m_data.m_value and @a m_data.m_type have +the following relationship: +- If `m_data.m_type == value_t::object`, then `m_data.m_value.object != nullptr`. +- If `m_data.m_type == value_t::array`, then `m_data.m_value.array != nullptr`. +- If `m_data.m_type == value_t::string`, then `m_data.m_value.string != nullptr`. +- If `m_data.m_type == value_t::binary`, then `m_data.m_value.binary != nullptr`. The invariants are checked by member function assert_invariant(). @note ObjectType trick from https://stackoverflow.com/a/9860911 @@ -26762,18 +26720,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } private: - using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; - template - using internal_iterator = ::nlohmann::detail::internal_iterator; template using iter_impl = ::nlohmann::detail::iter_impl; template using iteration_proxy = ::nlohmann::detail::iteration_proxy; template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; - template - using output_adapter_t = ::nlohmann::detail::output_adapter_t; - template using binary_reader = ::nlohmann::detail::binary_reader; template using binary_writer = ::nlohmann::detail::binary_writer; @@ -26914,8 +26866,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec std::to_string(__GNUC_PATCHLEVEL__)) } }; -#elif defined(__HP_cc) || defined(__HP_aCC) - result["compiler"] = "hp" +#elif defined(__HP_aCC) + result["compiler"] = {{"family", "hp"}, {"version", __HP_aCC}}; #elif defined(__IBMCPP__) result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; #elif defined(_MSC_VER) @@ -27057,9 +27009,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary | binary | pointer to @ref binary_t null | null | *no value is stored* - @note Variable-length types (objects, arrays, and strings) are stored as - pointers. The size of the union should not exceed 64 bits if the default - value types are used. + @note Variable-length types (objects, arrays, strings, and binary + values) are stored as pointers. The size of the union should not exceed + 64 bits if the default value types are used. @since version 1.0.0 */ @@ -27311,7 +27263,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec end of every constructor to make sure that created objects respect the invariant. Furthermore, it has to be called each time the type of a JSON value is changed, because the invariant expresses a relationship between - @a m_type and @a m_value. + @a m_data.m_type and @a m_data.m_value. Furthermore, the parent relation is checked for arrays and objects: If @a check_parents true and the value is an array or object, then the @@ -27332,7 +27284,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #if JSON_DIAGNOSTICS JSON_TRY { - // cppcheck-suppress assertWithSideEffect JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j) { return j.m_parent == this; @@ -28504,8 +28455,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief create a null object /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ - basic_json(std::nullptr_t = nullptr) noexcept // NOLINT(bugprone-exception-escape) - : basic_json(value_t::null) + basic_json(std::nullptr_t = nullptr) noexcept { assert_invariant(); } @@ -28869,15 +28819,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief move constructor /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept - : json_base_class_t(std::forward(other)), - m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check + : json_base_class_t(std::move(static_cast(other))), + m_data(std::move(other.m_data)) #if JSON_DIAGNOSTIC_POSITIONS - , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check - , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check + , start_position(other.start_position) + , end_position(other.end_position) #endif { // check that the passed value is valid - other.assert_invariant(false); // cppcheck-suppress[accessForwarded] + other.assert_invariant(false); // invalidate payload other.m_data.m_type = value_t::null; @@ -29444,7 +29394,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. + @ref number_unsigned_t, @ref number_float_t, or @ref binary_t. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -29612,6 +29562,90 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} + private: + /// @brief look up @a key in the object held by @a j, for either constness of @a j + /// @note the single place that performs a (possibly transparent) object key lookup + template + static auto object_lookup(Self& j, KeyType&& key) + -> decltype(j.m_data.m_value.object->find(lookup_key(std::forward(key)))) + { + return j.m_data.m_value.object->find(lookup_key(std::forward(key))); + } + + /// @brief checked object element access used by the at() overloads taking a key + /// @throw type_error.304 if @a j is not an object + /// @throw out_of_range.403 if @a key is not found + template + static auto object_at(Self& j, KeyType&& key) + -> decltype((object_lookup(j, std::forward(key))->second)) + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + } + + auto it = object_lookup(j, std::forward(key)); + if (it == j.m_data.m_value.object->end()) + { + // key is only forwarded into the lookup above: object_t::find() (a plain + // std::map or ordered_map) never moves from its argument, so key is still + // valid here regardless of whether KeyType was deduced as an rvalue reference + // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) + JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(key), "' not found"), &j)); + } + return it->second; + } + + /// @brief checked array element access used by the at() overloads taking an index + /// @throw type_error.304 if @a j is not an array + /// @throw out_of_range.401 if @a idx is out of range + template + static auto array_at(Self& j, size_type idx) + -> decltype((*j.m_data.m_value.array)[idx]) + { + // at only works for arrays + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + } + + if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) + { + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + } + + return (*j.m_data.m_value.array)[idx]; + } + + /// @brief convert a null value to an empty container of type @a Container + /// @tparam Container array_t or object_t; any other type does not compile + template + void convert_null_to() + { + JSON_ASSERT(is_null()); + // create the container before touching the type, so a throwing + // allocation leaves this value as a valid null rather than a type + // tag with a dangling/null pointer behind it + set_container(create()); + assert_invariant(); + } + + /// @brief store a freshly created array and set the matching type + void set_container(array_t* array) noexcept + { + m_data.m_value.array = array; + m_data.m_type = value_t::array; + } + + /// @brief store a freshly created object and set the matching type + void set_container(object_t* object) noexcept + { + m_data.m_value.object = object; + m_data.m_type = value_t::object; + } + + public: //////////////////// // element access // //////////////////// @@ -29624,54 +29658,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/at/ reference at(size_type idx) { - // at only works for arrays - if (JSON_HEDLEY_UNLIKELY(!is_array())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - if (JSON_HEDLEY_UNLIKELY(idx >= m_data.m_value.array->size())) - { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } - - return set_parent((*m_data.m_value.array)[idx]); + return set_parent(array_at(*this, idx)); } /// @brief access specified array element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ const_reference at(size_type idx) const { - // at only works for arrays - if (JSON_HEDLEY_UNLIKELY(!is_array())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - if (JSON_HEDLEY_UNLIKELY(idx >= m_data.m_value.array->size())) - { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } - - return (*m_data.m_value.array)[idx]; + return array_at(*this, idx); } /// @brief access specified object element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ reference at(const typename object_t::key_type& key) { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(key); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); - } - return set_parent(it->second); + return set_parent(object_at(*this, key)); } /// @brief access specified object element with bounds checking @@ -29680,36 +29681,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_usable_as_basic_json_key_type::value, int> = 0> reference at(KeyType && key) { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); - } - return set_parent(it->second); + return set_parent(object_at(*this, std::forward(key))); } /// @brief access specified object element with bounds checking /// @sa https://json.nlohmann.me/api/basic_json/at/ const_reference at(const typename object_t::key_type& key) const { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(key); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); - } - return it->second; + return object_at(*this, key); } /// @brief access specified object element with bounds checking @@ -29718,18 +29697,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_usable_as_basic_json_key_type::value, int> = 0> const_reference at(KeyType && key) const { - // at only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); - } - - auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); - if (it == m_data.m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); - } - return it->second; + return object_at(*this, std::forward(key)); } /// @brief access specified array element @@ -29739,9 +29707,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value.array = create(); - assert_invariant(); + convert_null_to(); } // operator[] only works for arrays @@ -29807,9 +29773,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value.object = create(); - assert_invariant(); + convert_null_to(); } // operator[] only works for objects @@ -29829,7 +29793,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto it = m_data.m_value.object->find(key); + auto it = object_lookup(*this, key); JSON_ASSERT(it != m_data.m_value.object->end()); return it->second; } @@ -29860,9 +29824,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value.object = create(); - assert_invariant(); + convert_null_to(); } // operator[] only works for objects @@ -29884,7 +29846,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // const operator[] only works for objects if (JSON_HEDLEY_LIKELY(is_object())) { - auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); + auto it = object_lookup(*this, std::forward(key)); JSON_ASSERT(it != m_data.m_value.object->end()); return it->second; } @@ -29904,6 +29866,36 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::is_c_string_uncvref::value, string_t, typename std::decay::type >; + /// @brief look up @a key for value(), the single place shared by all key-based overloads + /// @throw type_error.306 if this is not an object + /// @return a pointer to the found value, or `nullptr` if @a key was not found + template + const basic_json* value_member(KeyType&& key) const + { + // value only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + const auto it = find(std::forward(key)); + return it != end() ? &*it : nullptr; + } + + /// @brief resolve @a ptr for value(), the single place shared by both json_pointer overloads + /// @throw type_error.306 if this is not an array or object + /// @return a pointer to the resolved value, or `nullptr` if @a ptr does not resolve + const basic_json* value_pointee(const json_pointer& ptr) const + { + // value only works for arrays and objects + if (JSON_HEDLEY_UNLIKELY(!is_structured())) + { + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + return ptr.get_checked_or_null(this); + } + public: // an integer literal 0 would otherwise convert to a null const char* and from there to key_type template::value, int> = 0> @@ -29917,20 +29909,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(key); - if (it != end()) - { - return it->template get(); - } - - return default_value; - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(key); + return found != nullptr ? found->template get() : default_value; } /// @brief access specified object element with default value @@ -29942,20 +29923,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ReturnType value(const typename object_t::key_type& key, ValueType && default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(key); - if (it != end()) - { - return it->template get(); - } - - return std::forward(default_value); - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(key); + return found != nullptr ? found->template get() : std::forward(default_value); } /// @brief access specified object element with default value @@ -29968,23 +29938,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ValueType value(KeyType && key, const ValueType& default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(std::forward(key)); - if (it != end()) - { - return it->template get(); - } - - return default_value; - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(std::forward(key)); + return found != nullptr ? found->template get() : default_value; } - /// @brief access specified object element via JSON Pointer with default value + /// @brief access specified object element with default value /// @sa https://json.nlohmann.me/api/basic_json/value/ template < class ValueType, class KeyType, class ReturnType = typename value_return_type::type, detail::enable_if_t < @@ -29995,20 +29954,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ReturnType value(KeyType && key, ValueType && default_value) const { - // value only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // If 'key' is found, return its value. Otherwise, return `default_value'. - const auto it = find(std::forward(key)); - if (it != end()) - { - return it->template get(); - } - - return std::forward(default_value); - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto* found = value_member(std::forward(key)); + return found != nullptr ? found->template get() : std::forward(default_value); } /// @brief access specified object element via JSON Pointer with default value @@ -30018,21 +29966,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ValueType value(const json_pointer& ptr, const ValueType& default_value) const { - // value only works for arrays and objects - if (JSON_HEDLEY_LIKELY(is_structured())) - { - // If the pointer resolves to a value, return it. Otherwise, return - // 'default_value'. - const auto* res = ptr.get_checked_or_null(this); - if (JSON_HEDLEY_LIKELY(res != nullptr)) - { - return res->template get(); - } - - return default_value; - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. + const auto* found = value_pointee(ptr); + return found != nullptr ? found->template get() : default_value; } /// @brief access specified object element via JSON Pointer with default value @@ -30043,21 +29980,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > ReturnType value(const json_pointer& ptr, ValueType && default_value) const { - // value only works for arrays and objects - if (JSON_HEDLEY_LIKELY(is_structured())) - { - // If the pointer resolves to a value, return it. Otherwise, return - // 'default_value'. - const auto* res = ptr.get_checked_or_null(this); - if (JSON_HEDLEY_LIKELY(res != nullptr)) - { - return res->template get(); - } - - return std::forward(default_value); - } - - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. + const auto* found = value_pointee(ptr); + return found != nullptr ? found->template get() : std::forward(default_value); } template < class ValueType, class BasicJsonType, detail::enable_if_t < @@ -30142,21 +30068,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); } - if (is_string()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.string); - std::allocator_traits::deallocate(alloc, m_data.m_value.string, 1); - m_data.m_value.string = nullptr; - } - else if (is_binary()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.binary); - std::allocator_traits::deallocate(alloc, m_data.m_value.binary, 1); - m_data.m_value.binary = nullptr; - } - + m_data.m_value.destroy(m_data.m_type); + m_data.m_value = {}; m_data.m_type = value_t::null; assert_invariant(); break; @@ -30208,27 +30121,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case value_t::string: case value_t::binary: { - if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() - || !last.m_it.primitive_iterator.is_end())) + if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); } - if (is_string()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.string); - std::allocator_traits::deallocate(alloc, m_data.m_value.string, 1); - m_data.m_value.string = nullptr; - } - else if (is_binary()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_data.m_value.binary); - std::allocator_traits::deallocate(alloc, m_data.m_value.binary, 1); - m_data.m_value.binary = nullptr; - } - + m_data.m_value.destroy(m_data.m_type); + m_data.m_value = {}; m_data.m_type = value_t::null; assert_invariant(); break; @@ -30284,7 +30184,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); } - const auto it = m_data.m_value.object->find(lookup_key(std::forward(key))); + const auto it = object_lookup(*this, std::forward(key)); if (it != m_data.m_value.object->end()) { m_data.m_value.object->erase(it); @@ -30361,7 +30261,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(key); + result.m_it.object_iterator = object_lookup(*this, key); } return result; @@ -30375,7 +30275,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(key); + result.m_it.object_iterator = object_lookup(*this, key); } return result; @@ -30391,7 +30291,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); + result.m_it.object_iterator = object_lookup(*this, std::forward(key)); } return result; @@ -30407,7 +30307,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_object()) { - result.m_it.object_iterator = m_data.m_value.object->find(lookup_key(std::forward(key))); + result.m_it.object_iterator = object_lookup(*this, std::forward(key)); } return result; @@ -30438,7 +30338,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT bool contains(const typename object_t::key_type& key) const { - return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end(); + return is_object() && object_lookup(*this, key) != m_data.m_value.object->end(); } /// @brief check the existence of an element in a JSON object @@ -30448,7 +30348,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT bool contains(KeyType && key) const { - return is_object() && m_data.m_value.object->find(lookup_key(std::forward(key))) != m_data.m_value.object->end(); + return is_object() && object_lookup(*this, std::forward(key)) != m_data.m_value.object->end(); } /// @brief check the existence of an element in a JSON object given a JSON pointer @@ -30813,9 +30713,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value = value_t::array; - assert_invariant(); + convert_null_to(); } // add the element to the array (move semantics) @@ -30846,9 +30744,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value = value_t::array; - assert_invariant(); + convert_null_to(); } // add the element to the array @@ -30878,9 +30774,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value = value_t::object; - assert_invariant(); + convert_null_to(); } // add the element to the object @@ -30934,9 +30828,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; - m_data.m_value = value_t::array; - assert_invariant(); + convert_null_to(); } // add the element to the array (perfect forwarding) @@ -30959,9 +30851,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; - m_data.m_value = value_t::object; - assert_invariant(); + convert_null_to(); } // add the element to the array (perfect forwarding) @@ -31021,7 +30911,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/insert/ iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { - return insert(std::move(pos), val); + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // moving into a local first keeps this safe even if val aliases + // an element of this array + basic_json tmp(std::move(val)); + return insert_iterator(pos, std::move(tmp)); + } + + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); } /// @brief inserts copies of element into array @@ -31197,14 +31102,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// value is an object; called first by both @ref update overloads void prepare_update() { - // implicitly convert a null value to an empty object; create the - // object before setting the type, so a throwing allocation leaves - // this value null + // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_value.object = create(); - m_data.m_type = value_t::object; - assert_invariant(); + convert_null_to(); } if (JSON_HEDLEY_UNLIKELY(!is_object())) @@ -31413,7 +31314,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) { - // swap only works for strings + // swap only works for binary values if (JSON_HEDLEY_LIKELY(is_binary())) { using std::swap; @@ -31429,7 +31330,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) { - // swap only works for strings + // swap only works for binary values if (JSON_HEDLEY_LIKELY(is_binary())) { using std::swap; @@ -31930,7 +31831,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool ignore_trailing_commas = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded] + auto p = parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + p.parse(true, result); return result; } @@ -31947,7 +31849,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool ignore_trailing_commas = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + auto p = parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + p.parse(true, result); return result; } @@ -31960,7 +31863,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool ignore_trailing_commas = false) { basic_json result; - parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + p.parse(true, result); return result; } @@ -32187,6 +32091,31 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } #endif + private: + /*! + @brief shared implementation of the binary from_cbor/from_msgpack/ + from_ubjson/from_bjdata/from_bon8/from_bson overloads + + Building the @ref binary_reader as a named local, rather than as a + temporary that @ref detail::json_sax_dom_parser::parse is called on in + the same expression, avoids a false-positive cppcheck accessMoved + warning in each of the 16 callers. + */ + template + static basic_json from_binary_impl(InputAdapterType ia, const input_format_t format, + const bool strict, const bool allow_exceptions, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + binary_reader reader(std::move(ia), format); + if (!reader.sax_parse(&sdp, strict, tag_handler)) + { + result = value_t::discarded; + } + return result; + } + ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// @@ -32359,14 +32288,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::cbor, strict, allow_exceptions, tag_handler); } /// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32379,14 +32301,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::cbor, strict, allow_exceptions, tag_handler); } template @@ -32407,15 +32322,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, tag_handler); } /// @brief create a JSON value from an input in MessagePack format @@ -32426,14 +32333,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::msgpack, strict, allow_exceptions); } /// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32445,14 +32345,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::msgpack, strict, allow_exceptions); } template @@ -32471,15 +32364,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::msgpack, strict, allow_exceptions); } /// @brief create a JSON value from an input in UBJSON format @@ -32490,14 +32375,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::ubjson, strict, allow_exceptions); } /// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32509,14 +32387,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::ubjson, strict, allow_exceptions); } template @@ -32535,15 +32406,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::ubjson, strict, allow_exceptions); } /// @brief create a JSON value from an input in BJData format @@ -32554,14 +32417,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bjdata, strict, allow_exceptions); } /// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32573,14 +32429,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions); } /// @brief create a JSON value from an input in BON8 format @@ -32591,14 +32440,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bon8, strict, allow_exceptions); } /// @brief create a JSON value from an input in BON8 format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32610,14 +32452,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bon8, strict, allow_exceptions); } /// @brief create a JSON value from an input in BSON format @@ -32628,14 +32463,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::forward(i)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bson, strict, allow_exceptions); } /// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32647,14 +32475,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = detail::input_adapter(std::move(first), std::move(last)); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - if (!binary_reader(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bson, strict, allow_exceptions); } template @@ -32673,15 +32494,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true) { - basic_json result; - auto ia = i.get(); - detail::json_sax_dom_parser sdp(result, allow_exceptions); - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - if (!binary_reader(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved] - { - result = value_t::discarded; - } - return result; + return from_binary_impl(i.get(), input_format_t::bson, strict, allow_exceptions); } /// @} diff --git a/tests/src/unit-wstring.cpp b/tests/src/unit-wstring.cpp index b553ce246..c71684002 100644 --- a/tests/src/unit-wstring.cpp +++ b/tests/src/unit-wstring.cpp @@ -8,6 +8,7 @@ #include "doctest_compatibility.h" +#include #include using nlohmann::json; @@ -68,15 +69,15 @@ TEST_CASE("wide strings") CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); // a lone low surrogate cannot start a pair - CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&); // a high surrogate followed by a non-low-surrogate unit is invalid - CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&); // ... also when the unit is above the low surrogates - CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&); // a lone low surrogate must not swallow the following unit: pairing // it with any second unit would produce valid UTF-8, so the error // has to report an ill-formed byte at the surrogate's own position - CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&); // a valid surrogate pair is still decoded (U+1F600) CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get() == "\xF0\x9F\x98\x80"); } @@ -104,4 +105,55 @@ TEST_CASE("wide strings") // the same unit inside a string is reported as an ill-formed byte CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&); } + + SECTION("malformed wide-string input outside strings (#5645)") + { + json _; + + // a lone low surrogate inside a literal must not be truncated to its + // low byte and mistaken for the letter the literal expects next + // (0xDC72 truncates to 'r', which is what "true" expects after 't') + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u't', static_cast(0xDC72), u'u', u'e'}), + "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't\xFF'", json::parse_error&); + + // ... also when the lone surrogate is the last unit of the input + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'f', u'a', u'l', u's', static_cast(0xDD65)}), + "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'fals\xFF'", json::parse_error&); + + // a high surrogate followed by a unit that is not its low surrogate + // must not silently swallow that unit + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u't', static_cast(0xD872), u'X', u'u', u'e'}), + "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't\xFF'", json::parse_error&); + + // ... in particular, if the swallowed unit is the newline that ends a + // // comment, the comment must not extend over the following line + CHECK(json::parse(std::u16string{u'[', u'1', u' ', u'/', u'/', static_cast(0xD800), u'\n', + u',', u'2', u' ', u'/', u'/', u'\n', u']'}, + nullptr, true, /*ignore_comments*/true) == json::parse("[1,2]")); + CHECK(json::accept(std::u16string{u'[', u'1', u' ', u'/', u'/', static_cast(0xD800), u'\n', + u',', u'2', u' ', u'/', u'/', u'\n', u']'}, /*ignore_comments*/true)); + + // cases 5 and 6 use a 32-bit wchar_t (Linux, macOS, the BSDs) to reach + // the UTF-32 helper tested above via u32string; the 16-bit wchar_t of + // Windows goes through the UTF-16 helper instead, already covered by + // the u16string cases above +#if WCHAR_MAX > 0xFFFFu + // a negative wchar_t must not be mistaken for + // char_traits::eof() and silently end the input, letting + // trailing garbage pass the strict end-of-input check (only observable + // where wint_t is signed, e.g. macOS/the BSDs; on Linux wint_t is + // unsigned and this was already handled by #5348) + std::wstring w = L"[1]"; + w.push_back(static_cast(-1)); + w += L"garbage"; + CHECK(!json::accept(w)); + CHECK_THROWS_WITH_AS(_ = json::parse(w), + "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&); + + // other negative wchar_t units must not be truncated to their low + // byte (0xFFFFFF72 truncates to 'r', as in the u16string case above) + CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L't', static_cast(0xFFFFFF72), L'u', L'e'}), + "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 't\xFF'", json::parse_error&); +#endif + } } From 1a77948c25eee071319f2f07751455f884e25859 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 11:59:34 +0200 Subject: [PATCH 22/27] Document the macros that preview version 4.0 in the roadmap (#5593) * Document the macros that preview version 4.0 in the roadmap List the macros that guard breaking changes planned to become the default in version 4.0.0, and explain that 4.0.0 will be the sum of these opt-in flags, which can be tried on the 3.x release train. Signed-off-by: Niels Lohmann * List the deprecated functions removed in 4.0 in the roadmap The roadmap lists what will be removed; the migration guide keeps the examples for how to replace each item. Also mention the deprecated (ptr, len) overloads of the from_* functions in the migration guide. Signed-off-by: Niels Lohmann * Wrap overlong line in cbor_tag_handler_t documentation The line added in #5559 exceeds the 160-character limit enforced by the documentation style check. Signed-off-by: Niels Lohmann * Add JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON to the 4.0 example Signed-off-by: Niels Lohmann * Add JSON_STRICT_BINARY_UTF8 to the 4.0 roadmap The macro comes from #5741: the CBOR, UBJSON, BJData, and BSON writers keep writing ill-formed UTF-8 unchanged in 3.x and are planned to check it by default in 4.0. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../macros/json_brace_init_copy_semantics.md | 1 + docs/mkdocs/docs/community/roadmap.md | 70 +++++++++++++++++-- .../docs/integration/migration_guide.md | 7 +- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md index 459818c87..71b8bb28f 100644 --- a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md +++ b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md @@ -115,3 +115,4 @@ The default value is `0` (disabled — existing behavior is preserved). ## Version history - Added in version 3.13.0. +- Planned to become the default (with the macro removed) in version 4.0.0. diff --git a/docs/mkdocs/docs/community/roadmap.md b/docs/mkdocs/docs/community/roadmap.md index e8c407d3f..48977b295 100644 --- a/docs/mkdocs/docs/community/roadmap.md +++ b/docs/mkdocs/docs/community/roadmap.md @@ -14,7 +14,7 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js opt-in. - **Keep the 3.x public API stable.** Releases follow [semantic versioning](https://semver.org). Changes that would break existing code are only added behind a feature macro, so users can opt in and test their code before a next - major release. + major release, see [Version 4.0](#version-40). - **Support a broad range of compilers and platforms.** The [CI](quality_assurance.md) keeps testing old and new versions of GCC, Clang, MSVC, and other compilers on Linux, macOS, and Windows. - **Keep the quality assurance up.** Every change keeps the test coverage at 100%, passes the static and dynamic @@ -37,7 +37,67 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js ## Version 4.0 -There is no decision yet on whether or when a version 4.0 with breaking changes will be released. Proposals that need -a major version, for instance stricter type conversions, are collected in issue -[#3453](https://github.com/nlohmann/json/issues/3453). Until then, such changes are only added as opt-in behavior -behind feature macros. +There is no release date for version 4.0 yet. Proposals that need a major version, for instance stricter type +conversions, are collected in issue [#3453](https://github.com/nlohmann/json/issues/3453). + +!!! note "Not final" + + The plan for version 4.0 described below is not final and may still change: macros may be added to or removed from + the list, and planned defaults may be revised. Any such change will be documented on this page. + +### Trying out 4.0 today + +Version 4.0 will not be developed on a separate branch. Instead, every breaking change is first added to a 3.x release +behind a macro whose default keeps the 3.x behavior. Version 4.0 then switches the defaults and removes the macros. +Version 4.0 is therefore the sum of these macros: you can try it on the 3.x release train today by defining each macro +to its 4.0 value and fixing what no longer compiles or behaves differently. Once your code works with all of them, it +is ready for version 4.0. + +The following macros guard changes that are planned to become the default in version 4.0: + +| Macro | 3.x default | 4.0 behavior | CMake option | Added | +|------------------------------------------------------------------------------------------------------------------|-------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|--------| +| [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) | `1` | `0`: no implicit conversions from `basic_json` to other types; use [`get`](../api/basic_json/get.md) instead | [`JSON_ImplicitConversions`](../integration/cmake.md#json_implicitconversions) | 3.9.0 | +| [`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md) | `1` | `0`: the string literals `_json` and `_json_pointer` are only available in namespace `nlohmann::literals` | [`JSON_GlobalUDLs`](../integration/cmake.md#json_globaludls) | 3.11.0 | +| [`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md) | `0` | removed: the deprecated legacy comparison of discarded values can no longer be enabled | [`JSON_LegacyDiscardedValueComparison`](../integration/cmake.md#json_legacydiscardedvaluecomparison) | 3.11.0 | +| [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) | `0` | `1`: single-element brace initialization such as `#!cpp json j{obj};` copies the element instead of creating an array | – | 3.13.0 | +| [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) | `0` | `1`: reading from a stream does not consume the character after a number | – | 3.13.0 | +| [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) | `0` | `1`: a NUL byte in the input is a parse error instead of the end of input | [`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) | 3.13.0 | +| [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) | `0` | `1`: `to_cbor`, `to_ubjson`, `to_bjdata`, and `to_bson` throw for strings that are not valid UTF-8 by default | [`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) | 3.13.0 | + +For example, the following makes a 3.x release behave like version 4.0 with respect to these changes: + +```cpp +#define JSON_USE_IMPLICIT_CONVERSIONS 0 +#define JSON_USE_GLOBAL_UDLS 0 +#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#define JSON_BRACE_INIT_COPY_SEMANTICS 1 +#define JSON_PRECISE_STREAM_POSITION 1 +#define JSON_STRICT_NUL_HANDLING 1 +#define JSON_STRICT_BINARY_UTF8 1 +#include +``` + +The macros must be defined before the library header is included; setting them once in the build system is the easiest +way to achieve this. + +### Removal of deprecated functions + +Version 4.0 will remove all deprecated functions. Compiling with deprecation warnings enabled shows which of them your +code still uses. The [migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to +replace each of them. + +| Deprecated | Since | Migration | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------| +| `#!cpp operator<<(basic_json&, std::istream&)` | 3.0.0 | [Parsing](../integration/migration_guide.md#parsing) | +| `#!cpp operator>>(const basic_json&, std::ostream&)` | 3.0.0 | [Miscellaneous functions](../integration/migration_guide.md#miscellaneous-functions) | +| `iterator_wrapper` | 3.1.0 | [Miscellaneous functions](../integration/migration_guide.md#miscellaneous-functions) | +| [`parse`](../api/basic_json/parse.md), [`accept`](../api/basic_json/accept.md), and [`sax_parse`](../api/basic_json/sax_parse.md) with an initializer list `{ptr, len}` or `{first, last}` | 3.8.0 | [Parsing](../integration/migration_guide.md#parsing) | +| [`from_bson`](../api/basic_json/from_bson.md), [`from_cbor`](../api/basic_json/from_cbor.md), [`from_msgpack`](../api/basic_json/from_msgpack.md), and [`from_ubjson`](../api/basic_json/from_ubjson.md) with `(ptr, len)` or an initializer list | 3.8.0 | [Parsing](../integration/migration_guide.md#parsing) | +| [`json_pointer::operator string_t`](../api/json_pointer/operator_string_t.md) | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) | +| [`json_pointer`](../api/json_pointer/index.md) with a `basic_json` type as template argument, and the overloads of `value`, `contains`, `operator[]`, and `at` accepting such a pointer | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) | +| Comparing a [`json_pointer`](../api/json_pointer/index.md) with a string via [`operator==`](../api/json_pointer/operator_eq.md) or [`operator!=`](../api/json_pointer/operator_ne.md) | 3.11.2 | [JSON Pointers](../integration/migration_guide.md#json-pointers) | + +The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above. + +New breaking changes will follow the same path: they are added to these tables when they land in a 3.x release. diff --git a/docs/mkdocs/docs/integration/migration_guide.md b/docs/mkdocs/docs/integration/migration_guide.md index 29d54b6df..8a07d48a3 100644 --- a/docs/mkdocs/docs/integration/migration_guide.md +++ b/docs/mkdocs/docs/integration/migration_guide.md @@ -2,11 +2,14 @@ This page collects some guidelines on how to future-proof your code for future versions of this library. For how to add the library to your project in the first place, see [Integration](index.md), [CMake](cmake.md), or -[Package Managers](package_managers.md). +[Package Managers](package_managers.md). The [roadmap](../community/roadmap.md#version-40) lists what will change in +version 4.0, including the macros that let you try its behavior with a 3.x release; this page describes how to adjust +your code. ## Replace deprecated functions -The following functions have been deprecated and will be removed in the next major version (i.e., 4.0.0). All +The following functions have been deprecated and will be removed in the next major version (i.e., 4.0.0), see the +[roadmap](../community/roadmap.md#removal-of-deprecated-functions) for an overview. All deprecations are annotated with [`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which function to use instead. From 40021f38fb306fc04b7b52173e8b2f6d1203c27d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 12:00:31 +0200 Subject: [PATCH 23/27] Add basic_json::as_base_class and document name conflicts with custom base classes (#5589) * Add basic_json::as_base_class and document name conflicts with custom base classes Members of basic_json hide members of a custom base class with the same name, and future releases may add members that hide ones accessible today. Document this in json_base_class_t and add as_base_class() to reach hidden members without spelling out the cast. Also make json_base_class_t a public member type. It was documented since 3.12.0, but declared private, so users could not name it. Supersedes #3899. Co-authored-by: Raphael Grimm <1005058+barcode@users.noreply.github.com> Signed-off-by: Niels Lohmann * Add as_base_class to the docset search index New public members get an entry in docs/docset/docSet.sql (as done for to_bon8/from_bon8 in #2998). Without it, the Dash/Zeal docset built from the documentation cannot find basic_json::as_base_class. Signed-off-by: Niels Lohmann * Silence clang-tidy for the hidden type_name() in the base class test ci_clang_tidy failed with readability-convert-member-functions-to-static on base_class_with_hidden_members::type_name(). It must stay a non-static member: the test shows that it is hidden by the non-static basic_json::type_name() and reachable through as_base_class(). Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann Co-authored-by: Raphael Grimm <1005058+barcode@users.noreply.github.com> --- docs/docset/docSet.sql | 1 + .../docs/api/basic_json/as_base_class.md | 53 ++++++++++++++ docs/mkdocs/docs/api/basic_json/index.md | 1 + .../docs/api/basic_json/json_base_class_t.md | 14 ++++ docs/mkdocs/docs/examples/as_base_class.cpp | 41 +++++++++++ .../mkdocs/docs/examples/as_base_class.output | 2 + docs/mkdocs/mkdocs.yml | 1 + include/nlohmann/json.hpp | 18 ++++- single_include/nlohmann/json.hpp | 18 ++++- tests/src/unit-custom-base-class.cpp | 70 +++++++++++++++++++ 10 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 docs/mkdocs/docs/api/basic_json/as_base_class.md create mode 100644 docs/mkdocs/docs/examples/as_base_class.cpp create mode 100644 docs/mkdocs/docs/examples/as_base_class.output diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 477b2f9fe..09802e43e 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -19,6 +19,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('format_as', 'Function', 'api/ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::as_base_class', 'Method', 'api/basic_json/as_base_class/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html'); diff --git a/docs/mkdocs/docs/api/basic_json/as_base_class.md b/docs/mkdocs/docs/api/basic_json/as_base_class.md new file mode 100644 index 000000000..769707140 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json/as_base_class.md @@ -0,0 +1,53 @@ +# nlohmann::basic_json::as_base_class + +```cpp +json_base_class_t& as_base_class() noexcept; +const json_base_class_t& as_base_class() const noexcept; +``` + +Returns a reference to this object as its custom base class [`json_base_class_t`](json_base_class_t.md). No copy is +made. + +Since `basic_json` derives from `json_base_class_t`, a member of `basic_json` hides any member of the custom base class +with the same name. This function makes such hidden members accessible again. + +## Return value + +reference to this object as [`json_base_class_t`](json_base_class_t.md) + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Constant. + +## Notes + +The function is equivalent to `static_cast(j)` (or `static_cast(j)`). + +## Examples + +??? example + + The example shows how to use `as_base_class` to access members of the custom base class that are hidden by members + of `basic_json`. + + ```cpp + --8<-- "examples/as_base_class.cpp" + ``` + + Output: + + ```json + --8<-- "examples/as_base_class.output" + ``` + +## See also + +- [json_base_class_t](json_base_class_t.md) - type of the custom base class + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/index.md b/docs/mkdocs/docs/api/basic_json/index.md index 866bda67a..fc934ca93 100644 --- a/docs/mkdocs/docs/api/basic_json/index.md +++ b/docs/mkdocs/docs/api/basic_json/index.md @@ -200,6 +200,7 @@ Direct access to the stored value of a JSON value. - [**get_ref**](get_ref.md) - get a reference value - [**operator ValueType**](operator_ValueType.md) - get a value - [**get_binary**](get_binary.md) - get a binary value +- [**as_base_class**](as_base_class.md) - access the custom base class ### Element access diff --git a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md index 0d1abc9d4..6f0026558 100644 --- a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md +++ b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md @@ -27,6 +27,18 @@ A `CustomBaseClass` with non-static data members forfeits `basic_json`'s [standard layout](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType) guarantee. See [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass). +#### Name conflicts + +Since `basic_json` derives from `CustomBaseClass`, members of `basic_json` hide members of `CustomBaseClass` with the +same name. Hidden members remain accessible via [`as_base_class`](as_base_class.md) or by casting the value to +`json_base_class_t`. + +!!! warning "Avoid generic member names" + + Future versions of the library may add members to `basic_json` that hide members of `CustomBaseClass` that are + accessible today. To reduce the risk of such conflicts, avoid generic names for the members of `CustomBaseClass`, + for instance by using a distinctive prefix. + ## Examples ??? example @@ -45,8 +57,10 @@ A `CustomBaseClass` with non-static data members forfeits `basic_json`'s ## See also +- [as_base_class](as_base_class.md) - access the custom base class - [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass) - the requirements for `CustomBaseClass` ## Version history - Added in version 3.12.0. +- Made a public member type in version 3.13.0; it was private before, so it could not be named outside the class. diff --git a/docs/mkdocs/docs/examples/as_base_class.cpp b/docs/mkdocs/docs/examples/as_base_class.cpp new file mode 100644 index 000000000..48357b045 --- /dev/null +++ b/docs/mkdocs/docs/examples/as_base_class.cpp @@ -0,0 +1,41 @@ +#include +#include + +class base_class_with_hidden_members +{ + public: + const char* type_name() const noexcept + { + return "my_type_name"; + } + + std::size_t size() const noexcept + { + return 42; + } +}; + +using json = nlohmann::basic_json < + std::map, + std::vector, + std::string, + bool, + std::int64_t, + std::uint64_t, + double, + std::allocator, + nlohmann::adl_serializer, + std::vector, + base_class_with_hidden_members + >; + +int main() +{ + json j = {1, 2, 3}; + + // the members of basic_json hide the members of the base class + std::cout << j.type_name() << ' ' << j.size() << '\n'; + + // access the hidden members of the base class + std::cout << j.as_base_class().type_name() << ' ' << j.as_base_class().size() << '\n'; +} diff --git a/docs/mkdocs/docs/examples/as_base_class.output b/docs/mkdocs/docs/examples/as_base_class.output new file mode 100644 index 000000000..5ca62a673 --- /dev/null +++ b/docs/mkdocs/docs/examples/as_base_class.output @@ -0,0 +1,2 @@ +array 3 +my_type_name 42 diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index b4daf34c3..7b5512084 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -116,6 +116,7 @@ nav: - 'accept': api/basic_json/accept.md - 'array': api/basic_json/array.md - 'array_t': api/basic_json/array_t.md + - 'as_base_class': api/basic_json/as_base_class.md - 'at': api/basic_json/at.md - 'back': api/basic_json/back.md - 'begin': api/basic_json/begin.md diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 89ba13233..07b625a89 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -162,7 +162,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; - using json_base_class_t = ::nlohmann::detail::json_base_class; JSON_PRIVATE_UNLESS_TESTED: // convenience aliases for types residing in namespace detail; @@ -216,6 +215,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using cbor_tag_handler_t = detail::cbor_tag_handler_t; /// how to encode BJData using bjdata_version_t = detail::bjdata_version_t; + /// base class used to inject custom functionality into each instance of basic_json + /// @sa https://json.nlohmann.me/api/basic_json/json_base_class_t/ + using json_base_class_t = ::nlohmann::detail::json_base_class; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -3023,6 +3025,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return *get_ptr(); } + /// @brief access the custom base class + /// @sa https://json.nlohmann.me/api/basic_json/as_base_class/ + json_base_class_t& as_base_class() noexcept + { + return static_cast(*this); + } + + /// @brief access the custom base class + /// @sa https://json.nlohmann.me/api/basic_json/as_base_class/ + const json_base_class_t& as_base_class() const noexcept + { + return static_cast(*this); + } + /// @} private: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3b74fd67f..16e87cc86 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -26699,7 +26699,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// workaround type for MSVC using basic_json_t = NLOHMANN_BASIC_JSON_TPL; - using json_base_class_t = ::nlohmann::detail::json_base_class; JSON_PRIVATE_UNLESS_TESTED: // convenience aliases for types residing in namespace detail; @@ -26753,6 +26752,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using cbor_tag_handler_t = detail::cbor_tag_handler_t; /// how to encode BJData using bjdata_version_t = detail::bjdata_version_t; + /// base class used to inject custom functionality into each instance of basic_json + /// @sa https://json.nlohmann.me/api/basic_json/json_base_class_t/ + using json_base_class_t = ::nlohmann::detail::json_base_class; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -29560,6 +29562,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return *get_ptr(); } + /// @brief access the custom base class + /// @sa https://json.nlohmann.me/api/basic_json/as_base_class/ + json_base_class_t& as_base_class() noexcept + { + return static_cast(*this); + } + + /// @brief access the custom base class + /// @sa https://json.nlohmann.me/api/basic_json/as_base_class/ + const json_base_class_t& as_base_class() const noexcept + { + return static_cast(*this); + } + /// @} private: diff --git a/tests/src/unit-custom-base-class.cpp b/tests/src/unit-custom-base-class.cpp index a6b9b9ea4..38b665793 100644 --- a/tests/src/unit-custom-base-class.cpp +++ b/tests/src/unit-custom-base-class.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include "doctest_compatibility.h" @@ -406,6 +408,74 @@ TEST_CASE("JSON Visit Node") CHECK(expected.empty()); } +// Test accessing members of a custom base class that are hidden by members of nlohmann::basic_json +class base_class_with_hidden_members +{ + public: + const char* type_name() const noexcept // NOLINT(readability-convert-member-functions-to-static) + { + return "custom type_name"; + } + + std::size_t size() const noexcept + { + return m_size; + } + + std::size_t m_size = 42; +}; + +using json_with_hidden_base_members = + nlohmann::basic_json < + std::map, + std::vector, + std::string, + bool, + std::int64_t, + std::uint64_t, + double, + std::allocator, + nlohmann::adl_serializer, + std::vector, + base_class_with_hidden_members + >; + +TEST_CASE("JSON Node as_base_class") +{ + using json = json_with_hidden_base_members; + + static_assert(std::is_same().as_base_class()), json::json_base_class_t&>::value, ""); + static_assert(std::is_same().as_base_class()), const json::json_base_class_t&>::value, ""); + static_assert(noexcept(std::declval().as_base_class()), ""); + static_assert(noexcept(std::declval().as_base_class()), ""); + + SECTION("non-const") + { + json j = {1, 2, 3}; + + CHECK(std::string(j.type_name()) == "array"); + CHECK(j.size() == 3); + CHECK(std::string(j.as_base_class().type_name()) == "custom type_name"); + CHECK(j.as_base_class().size() == 42); + CHECK(&j.as_base_class() == &static_cast(j)); + + j.as_base_class().m_size = 7; + CHECK(j.as_base_class().size() == 7); + CHECK(j.size() == 3); + } + + SECTION("const") + { + const json j = {1, 2, 3}; + + CHECK(std::string(j.type_name()) == "array"); + CHECK(j.size() == 3); + CHECK(std::string(j.as_base_class().type_name()) == "custom type_name"); + CHECK(j.as_base_class().size() == 42); + CHECK(&j.as_base_class() == &static_cast(j)); + } +} + // A custom base class with a const member: copy-constructible (initializing a // const member works fine), but not copy-/move-assignable (assigning one does // not). Used to check that copy construction never requires more than that. From f56b418c56dcbf7c05fe6cca72e2a4de2d072b99 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 12:13:48 +0200 Subject: [PATCH 24/27] Follow each binary format's UTF-8 rule: strict writers (CBOR/UBJSON/BJData/BSON), lenient readers (#5741) Signed-off-by: Niels Lohmann --- CMakeLists.txt | 6 + cmake/ci.cmake | 2 +- docs/mkdocs/docs/api/basic_json/to_bjdata.md | 7 +- docs/mkdocs/docs/api/basic_json/to_bson.md | 6 + docs/mkdocs/docs/api/basic_json/to_cbor.md | 8 ++ docs/mkdocs/docs/api/basic_json/to_ubjson.md | 5 + docs/mkdocs/docs/api/macros/index.md | 2 + .../api/macros/json_strict_binary_utf8.md | 97 +++++++++++++++ .../docs/features/binary_formats/bjdata.md | 16 +++ .../docs/features/binary_formats/bson.md | 16 +-- .../docs/features/binary_formats/cbor.md | 17 +-- .../features/binary_formats/messagepack.md | 15 +-- .../docs/features/binary_formats/ubjson.md | 16 +++ docs/mkdocs/docs/features/macros.md | 14 +++ docs/mkdocs/docs/features/namespace.md | 1 + docs/mkdocs/docs/home/exceptions.md | 13 ++- docs/mkdocs/docs/integration/cmake.md | 5 + docs/mkdocs/mkdocs.yml | 1 + include/nlohmann/detail/abi_macros.hpp | 19 ++- .../nlohmann/detail/input/binary_reader.hpp | 29 ++--- include/nlohmann/detail/macro_unscope.hpp | 1 + .../nlohmann/detail/output/binary_writer.hpp | 70 ++++++++++- include/nlohmann/detail/string_utils.hpp | 44 +------ single_include/nlohmann/json_fwd.hpp | 19 ++- tests/abi/config/default.cpp | 4 + tests/abi/config/noversion.cpp | 4 + tests/src/unit-binary_utf8_strict.cpp | 110 ++++++++++++++++++ tests/src/unit-bjdata.cpp | 37 ++++++ tests/src/unit-bson.cpp | 37 ++++++ tests/src/unit-cbor.cpp | 85 +++++++++++--- tests/src/unit-msgpack.cpp | 36 ++++-- tests/src/unit-ubjson.cpp | 37 ++++++ 32 files changed, 651 insertions(+), 128 deletions(-) create mode 100644 docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md create mode 100644 tests/src/unit-binary_utf8_strict.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f0a6771dc..b5092ae49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ option(JSON_Install "Install CMake targets during install option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON) option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF) +option(JSON_StrictBinaryUTF8 "Build with UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers enabled." OFF) if (JSON_CI) include(ci) @@ -118,6 +119,10 @@ if (JSON_StrictNulHandling) message(STATUS "Strict NUL-byte handling enabled (JSON_STRICT_NUL_HANDLING=1)") endif() +if (JSON_StrictBinaryUTF8) + message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)") +endif() + if (JSON_Diagnostic_Positions) message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)") endif() @@ -153,6 +158,7 @@ target_compile_definitions( $<$:JSON_DIAGNOSTIC_POSITIONS=1> $<$:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1> $<$:JSON_STRICT_NUL_HANDLING=1> + $<$:JSON_STRICT_BINARY_UTF8=1> ) target_include_directories( diff --git a/cmake/ci.cmake b/cmake/ci.cmake index e67aba6d3..2aceb5afd 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -701,7 +701,7 @@ ci_get_cmake(4.0.0 CMAKE_4_0_0_BINARY) # the tests require CMake 3.13 or later, so they are excluded for CMake 3.5.0 set(JSON_CMAKE_FLAGS_3_5_0 JSON_Diagnostics JSON_Diagnostic_Positions JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind - JSON_StrictNulHandling) + JSON_StrictNulHandling JSON_StrictBinaryUTF8) set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0}) set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0}) diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index 44cc399e1..63dc5379e 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -56,6 +56,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` is false, and `j` contains a non-empty array, object, or binary value. +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not + valid UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are + written unchanged ## Complexity @@ -104,4 +107,6 @@ Linear in the size of the JSON value `j`. ## Version history - Added in version 3.11.0. -- BJData version parameter (for draft3 binary encoding) added in version 3.12.0. \ No newline at end of file +- BJData version parameter (for draft3 binary encoding) added in version 3.12.0. +- Throwing `type_error.316` for a string or object key that is not valid UTF-8 if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled added in version 3.13.0. \ No newline at end of file diff --git a/docs/mkdocs/docs/api/basic_json/to_bson.md b/docs/mkdocs/docs/api/basic_json/to_bson.md index c79f39ffc..e3104b13c 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bson.md +++ b/docs/mkdocs/docs/api/basic_json/to_bson.md @@ -46,6 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value exceeds 255, the maximum of the BSON binary subtype; example: `"subtype 70000 is too large for the BSON binary subtype (max 255)"` +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is not valid + UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are + written unchanged ## Complexity @@ -98,3 +101,6 @@ pass before anything is written. - Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. - Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0. - `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0. +- Throwing `type_error.316` for a string value or object key that is not valid UTF-8 if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, detected before anything is written, + added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/to_cbor.md b/docs/mkdocs/docs/api/basic_json/to_cbor.md index 3bbd9c7d3..cac8a0917 100644 --- a/docs/mkdocs/docs/api/basic_json/to_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/to_cbor.md @@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ Strong guarantee: if an exception is thrown, there are no changes in the JSON value. +## Exceptions + +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not + valid UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are + written unchanged + ## Complexity Linear in the size of the JSON value `j`. @@ -68,3 +74,5 @@ Linear in the size of the JSON value `j`. - Added in version 2.0.9. - Compact representation of floating-point numbers added in version 3.8.0. +- Throwing `type_error.316` for a string or object key that is not valid UTF-8 if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/to_ubjson.md b/docs/mkdocs/docs/api/basic_json/to_ubjson.md index 1b7f7767e..8f2ea5eb9 100644 --- a/docs/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/to_ubjson.md @@ -49,6 +49,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` is false, and `j` contains a non-empty array, object, or binary value. +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not + valid UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are + written unchanged ## Complexity @@ -97,3 +100,5 @@ Linear in the size of the JSON value `j`. ## Version history - Added in version 3.1.0. +- Throwing `type_error.316` for a string or object key that is not valid UTF-8 if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index 3c9b42af2..872d6cb8c 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -18,6 +18,8 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned right after a parsed number +- [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR, + UBJSON, BJData, and BSON writers - [**JSON_STRICT_NUL_HANDLING**](json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of treating it as end of input diff --git a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md new file mode 100644 index 000000000..7fe6d9a58 --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md @@ -0,0 +1,97 @@ +# JSON_STRICT_BINARY_UTF8 + +```cpp +#define JSON_STRICT_BINARY_UTF8 /* value */ +``` + +When defined to `1`, the binary writers [`to_cbor`](../basic_json/to_cbor.md), [`to_ubjson`](../basic_json/to_ubjson.md), +[`to_bjdata`](../basic_json/to_bjdata.md), and [`to_bson`](../basic_json/to_bson.md) check every string value and +object key for valid UTF-8 and throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for +ill-formed UTF-8, like [`dump`](../basic_json/dump.md) does. Without it, they write the bytes unchanged. + +The macro does not affect: + +- [`to_msgpack`](../basic_json/to_msgpack.md): the MessagePack specification allows a `str` value to contain bytes that + are not valid UTF-8, so it always writes them unchanged. +- [`to_bon8`](../basic_json/to_bon8.md): BON8 always checks, because the UTF-8 lead bytes mark where a string ends. +- The binary readers ([`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md), + [`from_ubjson`](../basic_json/from_ubjson.md), [`from_bjdata`](../basic_json/from_bjdata.md), + [`from_bson`](../basic_json/from_bson.md)): none of these formats requires a decoder to reject ill-formed UTF-8, so + they always return the bytes unchanged. + +## Default definition + +The default value is `0` (disabled, the behavior of version 3.12.0 and earlier is preserved). + +```cpp +#define JSON_STRICT_BINARY_UTF8 0 +``` + +## Notes + +!!! note "Background" + + CBOR, UBJSON, BJData, and BSON all require strings to be UTF-8. Up to version 3.12.0, the writers did not check + this, so they could produce output that other decoders reject. Checking by default would break code that stores + other encodings (for instance ISO 8859-1) in a string and only ever writes it to a binary format, so this macro + offers the check as an opt-in ahead of version 4.0.0, where it is planned to become the default (see + [#5529](https://github.com/nlohmann/json/issues/5529) and [#5651](https://github.com/nlohmann/json/issues/5651)). + +!!! warning "Opt-in only" + + This macro must be defined **before** including ``. Defining it after the include has no + effect. + +!!! note "ABI compatibility" + + The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_sbu8`), resulting in + distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program + without One Definition Rule (ODR) violations, but they cannot exchange instances of library types. + +## Examples + +??? example "Default behavior (macro not defined)" + + Without the macro, the bytes are written unchanged: + + ```cpp + #include + + using json = nlohmann::json; + + int main() + { + auto v = json::to_cbor(json("\xFF")); + // v is {0x61, 0xFF} + } + ``` + +??? example "Opt-in check (macro defined to 1)" + + With the macro, ill-formed UTF-8 is rejected: + + ```cpp + #define JSON_STRICT_BINARY_UTF8 1 + #include + + using json = nlohmann::json; + + int main() + { + auto v = json::to_cbor(json("\xFF")); + // throws type_error.316: invalid UTF-8 byte at index 0: 0xFF + } + ``` + +## See also + +- [**to_cbor**](../basic_json/to_cbor.md) - create a CBOR serialization of a JSON value +- [**to_ubjson**](../basic_json/to_ubjson.md) - create a UBJSON serialization of a JSON value +- [**to_bjdata**](../basic_json/to_bjdata.md) - create a BJData serialization of a JSON value +- [**to_bson**](../basic_json/to_bson.md) - create a BSON serialization of a JSON value +- [**error_handler_t**](../basic_json/error_handler_t.md) - how [`dump`](../basic_json/dump.md) treats ill-formed UTF-8 + +## Version history + +- Added in version 3.13.0. +- Planned to become the default (with the macro removed) in version 4.0.0. diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md index 8e3ed41f6..12d1af51b 100644 --- a/docs/mkdocs/docs/features/binary_formats/bjdata.md +++ b/docs/mkdocs/docs/features/binary_formats/bjdata.md @@ -63,6 +63,13 @@ The library uses the following mapping from JSON values types to BJData types ac - strings with more than 18446744073709551615 bytes, i.e., 264-1 bytes (theoretical) +!!! warning "UTF-8 validation of string values and object keys" + + BJData strings must use UTF-8 encoding. By default, `to_bjdata()` writes the bytes of string values and object keys + unchanged, even if they are not valid UTF-8. If + [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead. + !!! info "Unused BJData markers" The following markers are not used in the conversion: @@ -208,6 +215,15 @@ The library maps BJData types to JSON value types as follows: The mapping is **complete** in the sense that any BJData value can be converted to a JSON value. +!!! warning "Ill-formed UTF-8 in string values and object keys" + + BJData strings must use UTF-8 encoding, but this is not enforced on read: `from_bjdata()` accepts a string + value or object key whose bytes are not valid UTF-8 and hands them back unchanged. However, + [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error + handler is passed that replaces or ignores the ill-formed bytes. By default, `to_bjdata()` writes such a value + back unchanged (see above). + !!! info "Round trips" A value returned by [`from_bjdata`](../../api/basic_json/from_bjdata.md) can be serialized with diff --git a/docs/mkdocs/docs/features/binary_formats/bson.md b/docs/mkdocs/docs/features/binary_formats/bson.md index cca11451e..245ed3ebf 100644 --- a/docs/mkdocs/docs/features/binary_formats/bson.md +++ b/docs/mkdocs/docs/features/binary_formats/bson.md @@ -109,14 +109,16 @@ The library maps BSON record types to JSON value types as follows: If BSON input must be validated for strict specification compliance, validate it separately before passing it to `from_bson()`. -!!! warning "UTF-8 validation of string values" +!!! warning "Ill-formed UTF-8 in string values" - The BSON specification requires `string` values (type `0x02`) to be valid UTF-8. This library validates the - bytes of every such string at decode time and rejects ill-formed UTF-8 with a - [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions` - set to `false`, a discarded value), rather than only failing later when the resulting value is dumped. Element - (key) names and `binary` values (type `0x05`) are unaffected and are never validated, since they are read - byte-by-byte as a C string, or are not required to hold text, respectively. + The BSON specification requires `string` values (type `0x02`) to be valid UTF-8, but this is not required of a + decoder. `from_bson()` accepts a `string` value whose bytes are not valid UTF-8 and hands them back unchanged. + However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error handler is + passed that replaces or ignores the ill-formed bytes. By default, `to_bson()` writes such a string value or element + (key) name unchanged; if [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it + throws the same exception instead. Element (key) names are never validated on read, since they are read byte-by-byte + as a C string. `binary` values (type `0x05`) are unaffected, since they are not required to hold text. ??? example "Example: deserialize a JSON value from BSON" diff --git a/docs/mkdocs/docs/features/binary_formats/cbor.md b/docs/mkdocs/docs/features/binary_formats/cbor.md index eb7bc7f41..66300c5fa 100644 --- a/docs/mkdocs/docs/features/binary_formats/cbor.md +++ b/docs/mkdocs/docs/features/binary_formats/cbor.md @@ -189,15 +189,16 @@ The library maps CBOR types to JSON value types as follows: ([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a general-purpose CBOR library instead. -!!! warning "UTF-8 validation of text strings" +!!! warning "Ill-formed UTF-8 in text strings" - [RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings - (major type 3) to be valid UTF-8. This library validates the bytes of every text string (object keys included) at - decode time and rejects ill-formed UTF-8 with a - [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with - `allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value is - dumped. Byte strings (major type 2) are unaffected and are never validated, since they are not required to hold - text. + [RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major + type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this. This library does not: + `from_cbor()` accepts a text string (object keys included) whose bytes are not valid UTF-8 and hands them back + unchanged. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error handler is + passed that replaces or ignores the ill-formed bytes. By default, `to_cbor()` writes such a value back unchanged; if + [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it throws the same exception + instead. Byte strings (major type 2) are unaffected, since they are not required to hold text. !!! warning "Tagged items" diff --git a/docs/mkdocs/docs/features/binary_formats/messagepack.md b/docs/mkdocs/docs/features/binary_formats/messagepack.md index 2e674252a..047944852 100644 --- a/docs/mkdocs/docs/features/binary_formats/messagepack.md +++ b/docs/mkdocs/docs/features/binary_formats/messagepack.md @@ -153,14 +153,15 @@ The library maps MessagePack types to JSON value types as follows: This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed on. Such input needs a general-purpose MessagePack library instead. -!!! warning "UTF-8 validation of string values" +!!! warning "Ill-formed UTF-8 in string values" - The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8. - This library validates the bytes of every such string (object keys included) at decode time and rejects - ill-formed UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, - with `allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting - value is dumped. `bin`/`ext`/`fixext` values are unaffected and are never validated, since they are not required - to hold text. + The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain + a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged. + This library follows that: `from_msgpack()` reads `str` bytes (object keys included) as-is, without validating + them, and `to_msgpack()` writes them back as-is, so such a value round-trips through `from_msgpack(to_msgpack(j))` + byte for byte. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read this way, unless an + error handler is passed that replaces or ignores the ill-formed bytes. ??? example "Example: deserialize a JSON value from MessagePack" diff --git a/docs/mkdocs/docs/features/binary_formats/ubjson.md b/docs/mkdocs/docs/features/binary_formats/ubjson.md index 37aa069e2..dbdff6e6c 100644 --- a/docs/mkdocs/docs/features/binary_formats/ubjson.md +++ b/docs/mkdocs/docs/features/binary_formats/ubjson.md @@ -47,6 +47,13 @@ The library uses the following mapping from JSON values types to UBJSON types ac - strings with more than 9223372036854775807 bytes (theoretical) +!!! warning "UTF-8 validation of string values and object keys" + + UBJSON's required string encoding is UTF-8. By default, `to_ubjson()` writes the bytes of string values and object + keys unchanged, even if they are not valid UTF-8. If + [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead. + !!! info "Unused UBJSON markers" The following markers are not used in the conversion: @@ -120,6 +127,15 @@ The library maps UBJSON types to JSON value types as follows: The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value. +!!! warning "Ill-formed UTF-8 in string values and object keys" + + UBJSON's required string encoding is UTF-8, but this is not enforced on read: `from_ubjson()` accepts a string + value or object key whose bytes are not valid UTF-8 and hands them back unchanged. However, + [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error + handler is passed that replaces or ignores the ill-formed bytes. By default, `to_ubjson()` writes such a value + back unchanged (see above). + ??? example "Example: deserialize a JSON value from UBJSON" ```cpp diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index 90bb352c3..681980315 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -138,6 +138,20 @@ using the library with compilers that do not fully support C++11 and may only wo See [full documentation of `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`](../api/macros/json_skip_unsupported_compiler_check.md). +## `JSON_STRICT_BINARY_UTF8` + +When defined to `1`, [`to_cbor`](../api/basic_json/to_cbor.md), [`to_ubjson`](../api/basic_json/to_ubjson.md), +[`to_bjdata`](../api/basic_json/to_bjdata.md), and [`to_bson`](../api/basic_json/to_bson.md) throw +[`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) for a string value or object key that is not +valid UTF-8. The default value is `0`, which writes the bytes unchanged as before version 3.13.0; this is planned to +become the default in version 4.0.0. + +The check can also be enabled with the CMake option +[`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) (`OFF` by default) which sets +`JSON_STRICT_BINARY_UTF8` accordingly. + +See [full documentation of `JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md). + ## `JSON_STRICT_NUL_HANDLING` When defined to `1`, a `'\0'` (NUL) byte anywhere in the input is rejected with `parse_error.101`, like any other diff --git a/docs/mkdocs/docs/features/namespace.md b/docs/mkdocs/docs/features/namespace.md index 577f5e221..dbddac13d 100644 --- a/docs/mkdocs/docs/features/namespace.md +++ b/docs/mkdocs/docs/features/namespace.md @@ -20,6 +20,7 @@ The complete default namespace name is derived as follows: `_bics`. - [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`. - [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`. + - [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`. - The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component) below. diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index c78aeaa68..a3797cf04 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -340,8 +340,9 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde ### json.exception.parse_error.113 A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a -string was read where one was required (for instance as a map key), the string's length specification is invalid, or -the string's bytes are not valid UTF-8. +string was read where one was required (for instance as a map key), or the string's length specification is invalid. +The bytes of a string itself are not checked for valid UTF-8 on read; see the ill-formed UTF-8 notes on the +individual [binary format](../features/binary_formats/index.md) pages for how such a string is handled afterward. CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other type (for instance integers or `null`) are therefore not supported; see the notes on @@ -364,9 +365,6 @@ type (for instance integers or `null`) are therefore not supported; see the note ``` [json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative ``` - ``` - [json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte - ``` ### json.exception.parse_error.114 @@ -749,6 +747,11 @@ The [`unflatten()`](../api/basic_json/unflatten.md) function only works for an o The [`dump()`](../api/basic_json/dump.md) function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. See the FAQ entry on [serializing untrusted or invalid UTF-8](faq.md#serializing-untrusted-or-invalid-utf-8) for background and the recommended fix. +If [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) is enabled, the binary writers +[`to_cbor()`](../api/basic_json/to_cbor.md), [`to_ubjson()`](../api/basic_json/to_ubjson.md), +[`to_bjdata()`](../api/basic_json/to_bjdata.md), and [`to_bson()`](../api/basic_json/to_bson.md) throw this exception +for a string value or object key that is not valid UTF-8 as well. + !!! failure "Example message" Calling `dump()` on a JSON value containing an ISO 8859-1 encoded string: diff --git a/docs/mkdocs/docs/integration/cmake.md b/docs/mkdocs/docs/integration/cmake.md index 785fc3ed6..9151d12be 100644 --- a/docs/mkdocs/docs/integration/cmake.md +++ b/docs/mkdocs/docs/integration/cmake.md @@ -212,6 +212,11 @@ Use the non-amalgamated version of the library. This option is `ON` by default. Treat the library headers like system headers (i.e., adding `SYSTEM` to the [`target_include_directories`](https://cmake.org/cmake/help/latest/command/target_include_directories.html) call) to check for this library by tools like Clang-Tidy. This option is `OFF` by default. +### `JSON_StrictBinaryUTF8` + +Check string values and object keys for valid UTF-8 in the CBOR, UBJSON, BJData, and BSON writers, by defining the +macro [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md). This option is `OFF` by default. + ### `JSON_StrictNulHandling` Reject a `'\0'` (NUL) byte in the input instead of treating it as end of input, by defining the macro diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 7b5512084..3cb25ba9b 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -307,6 +307,7 @@ nav: - 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md - 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md - 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md + - 'JSON_STRICT_BINARY_UTF8': api/macros/json_strict_binary_utf8.md - 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md - 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md - 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md diff --git a/include/nlohmann/detail/abi_macros.hpp b/include/nlohmann/detail/abi_macros.hpp index 0bace616a..0153c8706 100644 --- a/include/nlohmann/detail/abi_macros.hpp +++ b/include/nlohmann/detail/abi_macros.hpp @@ -46,6 +46,10 @@ #define JSON_STRICT_NUL_HANDLING 0 #endif +#ifndef JSON_STRICT_BINARY_UTF8 + #define JSON_STRICT_BINARY_UTF8 0 +#endif + #if JSON_DIAGNOSTICS #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #else @@ -82,14 +86,20 @@ #define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING #endif +#if JSON_STRICT_BINARY_UTF8 + #define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 _sbu8 +#else + #define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 +#endif + #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ @@ -98,7 +108,8 @@ NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \ NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \ NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \ - NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING) + NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \ + NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 86f00bef6..3d79ad41b 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -4044,28 +4044,13 @@ class binary_reader const NumberType len, string_t& result) { - // get_bytes() appends to result, and CBOR indefinite-length strings - // collect all their chunks in the same result; validating only the - // newly read bytes keeps the check linear in the input size - const std::size_t old_size = result.size(); - if (JSON_HEDLEY_UNLIKELY(!get_bytes(format, len, "string", result))) - { - return false; - } - - // RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications - // all require text strings to be valid UTF-8; reject anything else - // right here so malformed input is caught at decode time instead of - // only surfacing later as a type_error.316 when the value is dumped - // (which would defeat allow_exceptions=false / strict discarding). - if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size))) - { - return sax->parse_error(chars_read, get_token_string(), - parse_error::create(113, chars_read, - exception_message(format, "invalid string: ill-formed UTF-8 byte", "string"), nullptr)); - } - - return true; + // Strings are taken as is: none of CBOR (RFC 8949 §3.1 leaves the + // choice to the decoder), MessagePack (whose spec explicitly allows + // a str object to contain an invalid byte sequence), UBJSON, BJData, + // or BSON requires a decoder to reject ill-formed UTF-8. The bytes + // are kept unchanged; dump() and the binary writers are the ones + // that check them and report type_error.316 if they are not valid. + return get_bytes(format, len, "string", result); } /*! diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index 8e1d49842..55b2ac99e 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -42,6 +42,7 @@ #undef JSON_BRACE_INIT_COPY_SEMANTICS #undef JSON_PRECISE_STREAM_POSITION #undef JSON_STRICT_NUL_HANDLING + #undef JSON_STRICT_BINARY_UTF8 #endif #include diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 9d844d85e..da5aa0cf4 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -115,6 +115,8 @@ class binary_writer /*! @param[in] j JSON value to serialize + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string + value or an object key is not valid UTF-8 @throw type_error.317 if @a j is not an object */ void write_bson(const BasicJsonType& j) @@ -145,6 +147,8 @@ class binary_writer /*! @param[in] j JSON value to serialize + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string + value or an object key is not valid UTF-8 */ void write_cbor(const BasicJsonType& j) { @@ -211,6 +215,8 @@ class binary_writer case value_t::string: { + check_text_utf8(*j.m_data.m_value.string, j); + // step 1: write control byte and the string length write_cbor_head(0x60, j.m_data.m_value.string->size()); @@ -287,6 +293,11 @@ class binary_writer // step 2: write each element for (const auto& el : *j.m_data.m_value.object) { + // el.first is checked here, against the object as + // diagnostics context, because write_cbor(el.first) + // converts it to a temporary basic_json that would be + // used as the context instead + check_text_utf8(el.first, j); write_cbor(el.first); write_cbor(el.second); } @@ -629,6 +640,8 @@ class binary_writer @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false @param[in] bjdata_version which BJData version to use, default is draft2 + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string + value or an object key is not valid UTF-8 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, @@ -678,6 +691,8 @@ class binary_writer case value_t::string: { + check_text_utf8(*j.m_data.m_value.string, j); + if (add_prefix) { oa.write_character(to_char_type('S')); @@ -840,6 +855,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.object) { + check_text_utf8(el.first, j); write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); oa.write_characters( reinterpret_cast(el.first.data()), @@ -884,6 +900,10 @@ class binary_writer /*! @return The size of a BSON document entry header, including the id marker and the entry name size (and its null-terminator). + @throw out_of_range.409 if @a name contains U+0000, before anything is + written + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a name is + not valid UTF-8, before anything is written */ static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) { @@ -893,7 +913,8 @@ class binary_writer JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); } - static_cast(j); + check_text_utf8(name, j); + return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; } @@ -949,9 +970,21 @@ class binary_writer /*! @return The size of the BSON-encoded string in @a value + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a value + is not valid UTF-8, before anything is written + + @note The UTF-8 check is skipped if @a value is already too long for the + 32-bit BSON length field (@ref to_bson_length rejects it later, once + the size of the whole document is known); this also keeps the check + from reading past a StringType that reports a size larger than what + it actually holds. */ - static std::size_t calc_bson_string_size(const string_t& value) + static std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j) { + if (JSON_HEDLEY_LIKELY(value_in_range_of(value.size()))) + { + check_text_utf8(value, j); + } return sizeof(std::int32_t) + value.size() + 1ul; } @@ -1080,6 +1113,8 @@ class binary_writer is neither an object nor an array @throw out_of_range.415 if @a j is binary with a subtype that does not fit into a byte, before anything is written + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a j is a + string that is not valid UTF-8, before anything is written */ static std::size_t calc_bson_value_size(const BasicJsonType& j) { @@ -1101,7 +1136,7 @@ class binary_writer return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned); case value_t::string: - return calc_bson_string_size(*j.m_data.m_value.string); + return calc_bson_string_size(*j.m_data.m_value.string, j); case value_t::null: return 0ul; @@ -1214,6 +1249,8 @@ class binary_writer written @throw out_of_range.415 if a binary value's subtype does not fit into a byte, before anything is written + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string + value or a key is not valid UTF-8, before anything is written */ static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector& nested_sizes) { @@ -2092,7 +2129,7 @@ class binary_writer */ void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context) { - check_bon8_utf8(s, context); + check_utf8(s, context); // a string that follows another string terminates it if (string_open) @@ -2122,7 +2159,7 @@ class binary_writer @throw type_error.316 if @a s is not valid UTF-8; the message names the first byte of the first invalid or incomplete sequence */ - static void check_bon8_utf8(const string_t& s, const BasicJsonType& context) + static void check_utf8(const string_t& s, const BasicJsonType& context) { static_cast(context); // only used when exceptions are enabled const auto* data = reinterpret_cast(s.data()); @@ -2133,6 +2170,29 @@ class binary_writer } } + /*! + @brief check a CBOR, UBJSON, BJData, or BSON text string for valid UTF-8 + + The check only happens if JSON_STRICT_BINARY_UTF8 is enabled. Otherwise, + the bytes are written unchanged, as before version 3.13.0. MessagePack + always writes the bytes as is, and BON8 always checks them (see + @ref check_utf8). + + @param[in] s the string to check + @param[in] context the value that holds @a s (for diagnostics) + @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a s is + not valid UTF-8 + */ + static void check_text_utf8(const string_t& s, const BasicJsonType& context) + { +#if JSON_STRICT_BINARY_UTF8 + check_utf8(s, context); +#else + static_cast(s); + static_cast(context); +#endif + } + /*! @brief write an integer in the shortest encoding diff --git a/include/nlohmann/detail/string_utils.hpp b/include/nlohmann/detail/string_utils.hpp index 7c40f7395..2b6864d0d 100644 --- a/include/nlohmann/detail/string_utils.hpp +++ b/include/nlohmann/detail/string_utils.hpp @@ -117,13 +117,14 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally written by Bjƶrn Hoehrmann. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. -The library checks UTF-8 well-formedness (RFC 3629, section 4) in four +The library checks UTF-8 well-formedness (RFC 3629, section 4) in three places, which differ in speed, diagnostics, and how they read the input: -- decode() and @ref is_valid_utf8 below: the serializer (to escape and, in - strict mode, reject ill-formed UTF-8 when dumping a string) and the CBOR, - MessagePack, BSON, UBJSON and BJData readers (to reject ill-formed UTF-8 in - text strings at decode time). +- decode() below: the serializer, to escape and, in strict mode, reject + ill-formed UTF-8 when dumping a string. The CBOR, MessagePack, BSON, + UBJSON and BJData readers do not use it: none of those specs requires a + decoder to reject ill-formed UTF-8 in text strings, so the readers keep + the bytes as is and leave the check to dump() and the binary writers. - the per-lead-byte switch in lexer::scan_string(): JSON text, with a diagnostic for each kind of error. - validate_one_utf8() and valid_utf8_prefix() in string_scan.hpp: the lexer's @@ -178,38 +179,5 @@ inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std: return state; } -/*! -@brief check whether a string consists solely of valid UTF-8 - -Used by the CBOR/MessagePack/BSON/UBJSON binary readers to reject text -strings that are not valid UTF-8 at decode time (RFC 8949 §3.1 and the -MessagePack/BSON specifications all require text strings to be UTF-8), so -that malformed input is caught immediately instead of only surfacing later -as a type_error.316 when the resulting value is dumped. - -@param[in] s the string to check -@param[in] first index of the first byte to check; the bytes before it are - assumed to have been validated already and to end on a - code point boundary -@return whether @a s (from index @a first on) is valid UTF-8 -*/ -template -inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept -{ - std::uint8_t state = UTF8_ACCEPT; - std::uint32_t codepoint = 0; - - for (std::size_t i = first; i < s.size(); ++i) - { - decode(state, codepoint, static_cast(s[i])); - if (state == UTF8_REJECT) - { - return false; - } - } - - return state == UTF8_ACCEPT; -} - } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/single_include/nlohmann/json_fwd.hpp b/single_include/nlohmann/json_fwd.hpp index 3ee7afa73..7cadc9b1c 100644 --- a/single_include/nlohmann/json_fwd.hpp +++ b/single_include/nlohmann/json_fwd.hpp @@ -63,6 +63,10 @@ #define JSON_STRICT_NUL_HANDLING 0 #endif +#ifndef JSON_STRICT_BINARY_UTF8 + #define JSON_STRICT_BINARY_UTF8 0 +#endif + #if JSON_DIAGNOSTICS #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #else @@ -99,14 +103,20 @@ #define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING #endif +#if JSON_STRICT_BINARY_UTF8 + #define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 _sbu8 +#else + #define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 +#endif + #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ @@ -115,7 +125,8 @@ NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \ NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \ NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \ - NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING) + NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \ + NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ diff --git a/tests/abi/config/default.cpp b/tests/abi/config/default.cpp index 879322dd0..e4c627060 100644 --- a/tests/abi/config/default.cpp +++ b/tests/abi/config/default.cpp @@ -44,6 +44,10 @@ TEST_CASE("default namespace") expected += "_snul"; #endif +#if JSON_STRICT_BINARY_UTF8 + expected += "_sbu8"; +#endif + expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json"; diff --git a/tests/abi/config/noversion.cpp b/tests/abi/config/noversion.cpp index 4b1eb6ee4..858964695 100644 --- a/tests/abi/config/noversion.cpp +++ b/tests/abi/config/noversion.cpp @@ -45,6 +45,10 @@ TEST_CASE("default namespace without version component") expected += "_snul"; #endif +#if JSON_STRICT_BINARY_UTF8 + expected += "_sbu8"; +#endif + expected += "::basic_json"; // fallback for Clang diff --git a/tests/src/unit-binary_utf8_strict.cpp b/tests/src/unit-binary_utf8_strict.cpp new file mode 100644 index 000000000..c83b5938c --- /dev/null +++ b/tests/src/unit-binary_utf8_strict.cpp @@ -0,0 +1,110 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +// The binary writers check strings and object keys for valid UTF-8 only if +// JSON_STRICT_BINARY_UTF8 is enabled (planned to be the default in 4.0.0). +// Without it, they write the bytes unchanged, as before version 3.13.0; the +// tests for that are next to the other tests of each format. +#ifdef JSON_STRICT_BINARY_UTF8 + #undef JSON_STRICT_BINARY_UTF8 +#endif + +#define JSON_STRICT_BINARY_UTF8 1 + +#include +using nlohmann::json; + +#include +#include + +TEST_CASE("JSON_STRICT_BINARY_UTF8 (see #5529, #5651)") +{ + SECTION("CBOR") + { + // a string value with ill-formed UTF-8 is rejected + CHECK_THROWS_WITH_AS(json::to_cbor(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + // a truncated multi-byte sequence + CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&); + // an encoded surrogate half (U+D800) + CHECK_THROWS_WITH_AS(json::to_cbor(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&); + // an overlong encoding of '.' + CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&); + + // an object key with ill-formed UTF-8 is rejected the same way + CHECK_THROWS_WITH_AS(json::to_cbor(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + + // binary values are not text and are unaffected + CHECK_NOTHROW(json::to_cbor(json::binary(std::vector({0xFF})))); + + // a value read back from CBOR with ill-formed bytes cannot be written + // back either (the reader is lenient regardless of the macro) + const json j = json::from_cbor(std::vector({0x62, 0xc0, 0xae})); + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&); + } + + SECTION("UBJSON") + { + CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + // a truncated multi-byte sequence + CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&); + // an encoded surrogate half (U+D800) + CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&); + // an overlong encoding of '.' + CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&); + + // an object key with ill-formed UTF-8 is rejected the same way + CHECK_THROWS_WITH_AS(json::to_ubjson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + } + + SECTION("BJData") + { + CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + // a truncated multi-byte sequence + CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&); + // an encoded surrogate half (U+D800) + CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&); + // an overlong encoding of '.' + CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&); + + // an object key with ill-formed UTF-8 is rejected the same way + CHECK_THROWS_WITH_AS(json::to_bjdata(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + } + + SECTION("BSON") + { + // to_bson() rejects the same kind of ill-formed string value, before + // any bytes reach the output adapter (the BSON document length + // prefix must be known up front, so nothing is written incrementally) + std::vector out{0x42}; // a sentinel byte the writer must not touch + CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}, nlohmann::detail::output_adapter(out)), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + CHECK(out == std::vector {0x42}); + + CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + // a truncated multi-byte sequence + CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC3"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&); + // an encoded surrogate half (U+D800) + CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&); + // an overlong encoding of '.' + CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&); + + // an object key with ill-formed UTF-8 is rejected as well; unlike + // the reader (which never validates element names), the writer + // checks both string values and object keys + CHECK_THROWS_WITH_AS(json::to_bson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + } + + SECTION("MessagePack and BON8 are unaffected") + { + // MessagePack allows any bytes in a str, so to_msgpack() writes them as + // is; BON8 always checks, because the lead bytes mark where strings end + CHECK(json::to_msgpack(json("\xFF")) == std::vector({0xa1, 0xff})); + CHECK_THROWS_AS(json::to_bon8(json("\xFF")), json::type_error&); + } +} diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index b6be66c9e..f34df1717 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3906,6 +3906,43 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") CHECK(json::to_bjdata(j) == v); CHECK(json::from_bjdata(v) == j); } + + SECTION("ill-formed UTF-8 (see #5529, #5651)") + { + // none of the binary format specs requires a decoder to reject + // ill-formed UTF-8 in a text string, so a value whose bytes are + // not valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') + // round-trips byte for byte as a string value; to_bjdata() writes + // the bytes unchanged, as before 3.13.0, unless + // JSON_STRICT_BINARY_UTF8 is enabled (see + // unit-binary_utf8_strict.cpp) + const std::vector v = {'S', 'i', 2, 0xc0, 0xae}; + json j; + CHECK_NOTHROW(j = json::from_bjdata(v)); + REQUIRE(j.is_string()); + CHECK(j.get_ref() == std::string("\xc0\xae")); + CHECK_THROWS_AS(j.dump(), json::type_error&); + CHECK(json::from_bjdata(json::to_bjdata(j)) == j); + + // the same bytes as an object key round-trip as well + const std::vector v_key = {'{', 'i', 2, 0xc0, 0xae, 'i', 1, '}'}; + json j_key; + CHECK_NOTHROW(j_key = json::from_bjdata(v_key)); + REQUIRE(j_key.is_object()); + CHECK(j_key.contains(std::string("\xc0\xae"))); + CHECK(json::from_bjdata(json::to_bjdata(j_key)) == j_key); + + CHECK(json::from_bjdata(json::to_bjdata(json("\xFF"))) == json("\xFF")); + // a truncated multi-byte sequence + CHECK(json::from_bjdata(json::to_bjdata(json("\xC3"))) == json("\xC3")); + // an encoded surrogate half (U+D800) + CHECK(json::from_bjdata(json::to_bjdata(json("\xED\xA0\x80"))) == json("\xED\xA0\x80")); + // an overlong encoding of '.' + CHECK(json::from_bjdata(json::to_bjdata(json("\xC0\xAF"))) == json("\xC0\xAF")); + + // an object key with ill-formed UTF-8 is kept the same way + CHECK(json::from_bjdata(json::to_bjdata(json{{"\xFF", 1}})) == json{{"\xFF", 1}}); + } } SECTION("Array Type") diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 2f9a727ab..92a14e6fe 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -154,6 +154,43 @@ TEST_CASE("BSON") #endif } + SECTION("ill-formed UTF-8 (see #5529, #5651)") + { + // a BSON document {"s": "\xC0\xAE"} (0xC0 0xAE is an overlong + // encoding of '.'); the BSON spec does not require a decoder to + // reject ill-formed UTF-8 in a string value, so the reader hands the + // bytes back unchanged + const std::vector v = + { + 0x0F, 0x00, 0x00, 0x00, // document length + 0x02, 's', 0x00, // type 0x02 (string), key "s" + 0x03, 0x00, 0x00, 0x00, // string length (including null) + 0xc0, 0xae, 0x00, // string content and its null terminator + 0x00 // document terminator + }; + json j; + CHECK_NOTHROW(j = json::from_bson(v)); + REQUIRE(j.is_object()); + REQUIRE(j.contains("s")); + CHECK(j["s"].get_ref() == std::string("\xc0\xae")); + // dump() still requires valid UTF-8 and throws for such a value + CHECK_THROWS_AS(j.dump(), json::type_error&); + // to_bson() writes the bytes back unchanged, as before 3.13.0, + // unless JSON_STRICT_BINARY_UTF8 is enabled (see unit-binary_utf8_strict.cpp) + CHECK(json::from_bson(json::to_bson(j)) == j); + + CHECK(json::from_bson(json::to_bson(json{{"s", "\xFF"}})) == json{{"s", "\xFF"}}); + // a truncated multi-byte sequence + CHECK(json::from_bson(json::to_bson(json{{"s", "\xC3"}})) == json{{"s", "\xC3"}}); + // an encoded surrogate half (U+D800) + CHECK(json::from_bson(json::to_bson(json{{"s", "\xED\xA0\x80"}})) == json{{"s", "\xED\xA0\x80"}}); + // an overlong encoding of '.' + CHECK(json::from_bson(json::to_bson(json{{"s", "\xC0\xAF"}})) == json{{"s", "\xC0\xAF"}}); + + // an object key with ill-formed UTF-8 is kept as well + CHECK(json::from_bson(json::to_bson(json{{"\xFF", 1}})) == json{{"\xFF", 1}}); + } + SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON") { // out_of_range.412 is thrown from a single shared helper diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index c3a425a16..633f50fea 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -1801,19 +1801,41 @@ TEST_CASE("CBOR") CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&); } - SECTION("invalid UTF-8 in string (see #5529)") + SECTION("ill-formed UTF-8 in string (see #5529, #5651)") { + // RFC 8949 §3.1 leaves it up to the decoder whether to reject + // ill-formed UTF-8 in a text string; this library does not, and + // hands the original bytes back unchanged, matching the + // MessagePack reader and the behavior before #5185/#5531 (not in + // any release) + // a two-character text string (major type 3) whose bytes are not - // valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') must be - // rejected at decode time, matching every other kind of - // malformed binary input, rather than only failing later when - // the resulting value is dumped - json _; - CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0x62, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&); - CHECK(json::from_cbor(std::vector({0x62, 0xc0, 0xae}), true, false).is_discarded()); + // valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') round-trips + // byte for byte as a string value + const std::vector ill_formed_value = {0x62, 0xc0, 0xae}; + json j_value; + CHECK_NOTHROW(j_value = json::from_cbor(ill_formed_value)); + REQUIRE(j_value.is_string()); + CHECK(j_value.get_ref() == std::string("\xc0\xae")); + // dump() still requires valid UTF-8 and throws for such a value, + // unless an error handler that replaces or ignores the bytes is + // passed + CHECK_THROWS_AS(j_value.dump(), json::type_error&); + // to_cbor() writes the bytes back unchanged, as before 3.13.0, + // unless JSON_STRICT_BINARY_UTF8 is enabled (see unit-binary_utf8_strict.cpp) + CHECK(json::from_cbor(json::to_cbor(j_value)) == j_value); + + // the same bytes as an object key round-trip as well + const std::vector ill_formed_key = {0xa1, 0x62, 0xc0, 0xae, 0x01}; + json j_key; + CHECK_NOTHROW(j_key = json::from_cbor(ill_formed_key)); + REQUIRE(j_key.is_object()); + CHECK(j_key.contains(std::string("\xc0\xae"))); + CHECK(json::from_cbor(json::to_cbor(j_key)) == j_key); // a CBOR byte string (major type 2) with the very same bytes is // NOT text and must still be accepted as-is + json _; CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x42, 0xc0, 0xae}))); CHECK(_ == json::binary(std::vector({0xc0, 0xae}))); @@ -1822,17 +1844,47 @@ TEST_CASE("CBOR") CHECK(json::from_cbor(json::to_cbor(j)) == j); } - SECTION("invalid UTF-8 in indefinite-length string") + SECTION("to_cbor keeps ill-formed UTF-8 (see #5651)") + { + // to_cbor() writes the bytes unchanged, as before 3.13.0, unless + // JSON_STRICT_BINARY_UTF8 is enabled (see + // unit-binary_utf8_strict.cpp); from_cbor() reads them back as is + CHECK(json::from_cbor(json::to_cbor(json("\xFF"))) == json("\xFF")); + // a truncated multi-byte sequence + CHECK(json::from_cbor(json::to_cbor(json("\xC3"))) == json("\xC3")); + // an encoded surrogate half (U+D800) + CHECK(json::from_cbor(json::to_cbor(json("\xED\xA0\x80"))) == json("\xED\xA0\x80")); + // an overlong encoding of '.' + CHECK(json::from_cbor(json::to_cbor(json("\xC0\xAF"))) == json("\xC0\xAF")); + + // an object key with ill-formed UTF-8 is kept the same way + CHECK(json::from_cbor(json::to_cbor(json{{"\xFF", 1}})) == json{{"\xFF", 1}}); + + // binary values are not text and are unaffected + CHECK_NOTHROW(json::to_cbor(json::binary(std::vector({0xFF})))); + } + + SECTION("ill-formed UTF-8 in indefinite-length string") { json _; - // every chunk must be valid UTF-8 on its own (RFC 8949, Section - // 3.2.3), so a code point split across two chunks is rejected - CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&); - CHECK(json::from_cbor(std::vector({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}), true, false).is_discarded()); + // the chunks are concatenated as is, without checking that each + // chunk is valid UTF-8 on its own (RFC 8949, Section 3.2.3), so + // a code point split across two chunks yields a valid string + CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}))); + CHECK(_ == "\xc3\xa9"); + CHECK(_.dump() == "\"\xc3\xa9\""); - // an ill-formed later chunk is rejected after valid ones - CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc0, 0xae, 0xff})), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&); + // a truncated code point is kept as is + CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x7f, 0x61, 0xc3, 0xff}))); + CHECK(_ == "\xc3"); + CHECK_THROWS_AS(_.dump(), json::type_error&); + CHECK(json::from_cbor(json::to_cbor(_)) == _); + + // an ill-formed later chunk is kept after valid ones + CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc0, 0xae, 0xff}))); + CHECK(_ == "\xc3\xa9\xc0\xae"); + CHECK_THROWS_AS(_.dump(), json::type_error&); // valid multi-byte chunks are accepted CHECK(json::from_cbor(std::vector({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6"); @@ -1840,9 +1892,6 @@ TEST_CASE("CBOR") SECTION("many chunks in indefinite-length string") { - // only the newly read chunk is validated, not the whole string - // collected so far; validating the latter made this input take - // quadratic time (about ten seconds for 100000 chunks) constexpr std::size_t chunks = 100000; std::vector v{0x7f}; for (std::size_t i = 0; i < chunks; ++i) diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index afabb85c6..a92496144 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -1540,19 +1540,39 @@ TEST_CASE("MessagePack") CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector({0x81})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&); } - SECTION("invalid UTF-8 in string (see #5529)") + SECTION("ill-formed UTF-8 in string (see #5529, #5651)") { + // the MessagePack specification explicitly allows a str object to + // contain a byte sequence that is not valid UTF-8 and expects a + // deserializer to hand the original bytes back unchanged; this + // library follows that, unlike CBOR/UBJSON/BJData/BSON, whose + // specifications require text strings to be valid UTF-8 + // a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8 - // (0xC0 0xAE is an overlong encoding of '.') must be rejected at - // decode time, matching every other kind of malformed binary - // input, rather than only failing later when the resulting - // value is dumped - json _; - CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector({0xa2, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing MessagePack string: invalid string: ill-formed UTF-8 byte", json::parse_error&); - CHECK(json::from_msgpack(std::vector({0xa2, 0xc0, 0xae}), true, false).is_discarded()); + // (0xC0 0xAE is an overlong encoding of '.') round-trips byte for + // byte as a string value + const std::vector ill_formed_value = {0xa2, 0xc0, 0xae}; + json j_value; + CHECK_NOTHROW(j_value = json::from_msgpack(ill_formed_value)); + REQUIRE(j_value.is_string()); + CHECK(j_value.get_ref() == std::string("\xc0\xae")); + CHECK(json::from_msgpack(json::to_msgpack(j_value)) == j_value); + // dump() still requires valid UTF-8 and throws for such a value, + // unless an error handler that replaces or ignores the bytes is + // passed + CHECK_THROWS_AS(j_value.dump(), json::type_error&); + + // the same bytes as an object key round-trip as well + const std::vector ill_formed_key = {0x81, 0xa2, 0xc0, 0xae, 0x01}; + json j_key; + CHECK_NOTHROW(j_key = json::from_msgpack(ill_formed_key)); + REQUIRE(j_key.is_object()); + CHECK(j_key.contains(std::string("\xc0\xae"))); + CHECK(json::from_msgpack(json::to_msgpack(j_key)) == j_key); // a MessagePack bin8 blob with the very same bytes is NOT text // and must still be accepted as-is + json _; CHECK_NOTHROW(_ = json::from_msgpack(std::vector({0xc4, 0x02, 0xc0, 0xae}))); CHECK(_ == json::binary(std::vector({0xc0, 0xae}))); diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 9a9710b48..450882a12 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2505,6 +2505,43 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") CHECK(json::to_ubjson(j) == v); CHECK(json::from_ubjson(v) == j); } + + SECTION("ill-formed UTF-8 (see #5529, #5651)") + { + // none of the binary format specs requires a decoder to reject + // ill-formed UTF-8 in a text string, so a value whose bytes are + // not valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') + // round-trips byte for byte as a string value; to_ubjson() writes + // the bytes unchanged, as before 3.13.0, unless + // JSON_STRICT_BINARY_UTF8 is enabled (see + // unit-binary_utf8_strict.cpp) + const std::vector v = {'S', 'i', 2, 0xc0, 0xae}; + json j; + CHECK_NOTHROW(j = json::from_ubjson(v)); + REQUIRE(j.is_string()); + CHECK(j.get_ref() == std::string("\xc0\xae")); + CHECK_THROWS_AS(j.dump(), json::type_error&); + CHECK(json::from_ubjson(json::to_ubjson(j)) == j); + + // the same bytes as an object key round-trip as well + const std::vector v_key = {'{', 'i', 2, 0xc0, 0xae, 'i', 1, '}'}; + json j_key; + CHECK_NOTHROW(j_key = json::from_ubjson(v_key)); + REQUIRE(j_key.is_object()); + CHECK(j_key.contains(std::string("\xc0\xae"))); + CHECK(json::from_ubjson(json::to_ubjson(j_key)) == j_key); + + CHECK(json::from_ubjson(json::to_ubjson(json("\xFF"))) == json("\xFF")); + // a truncated multi-byte sequence + CHECK(json::from_ubjson(json::to_ubjson(json("\xC3"))) == json("\xC3")); + // an encoded surrogate half (U+D800) + CHECK(json::from_ubjson(json::to_ubjson(json("\xED\xA0\x80"))) == json("\xED\xA0\x80")); + // an overlong encoding of '.' + CHECK(json::from_ubjson(json::to_ubjson(json("\xC0\xAF"))) == json("\xC0\xAF")); + + // an object key with ill-formed UTF-8 is kept the same way + CHECK(json::from_ubjson(json::to_ubjson(json{{"\xFF", 1}})) == json{{"\xFF", 1}}); + } } SECTION("Array Type") From 73e9eae3c135e262dac3fe3e7978b46694e531d3 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 12:13:49 +0200 Subject: [PATCH 25/27] Add an error_handler parameter for UTF-8 to the binary readers and writers (#5746) Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/dump.md | 9 +- .../docs/api/basic_json/error_handler_t.md | 32 +- .../mkdocs/docs/api/basic_json/from_bjdata.md | 15 +- docs/mkdocs/docs/api/basic_json/from_bson.md | 15 +- docs/mkdocs/docs/api/basic_json/from_cbor.md | 18 +- .../docs/api/basic_json/from_msgpack.md | 18 +- .../mkdocs/docs/api/basic_json/from_ubjson.md | 15 +- docs/mkdocs/docs/api/basic_json/to_bjdata.md | 26 +- docs/mkdocs/docs/api/basic_json/to_bson.md | 28 +- docs/mkdocs/docs/api/basic_json/to_cbor.md | 26 +- docs/mkdocs/docs/api/basic_json/to_msgpack.md | 20 +- docs/mkdocs/docs/api/basic_json/to_ubjson.md | 26 +- .../api/macros/json_strict_binary_utf8.md | 18 +- docs/mkdocs/docs/examples/error_handler_t.cpp | 3 +- .../docs/examples/error_handler_t.output | 1 + .../docs/features/binary_formats/bjdata.md | 24 +- .../docs/features/binary_formats/bson.md | 19 +- .../docs/features/binary_formats/cbor.md | 17 +- .../features/binary_formats/messagepack.md | 18 +- .../docs/features/binary_formats/ubjson.md | 24 +- docs/mkdocs/docs/home/exceptions.md | 18 +- .../nlohmann/detail/input/binary_reader.hpp | 114 ++- .../nlohmann/detail/output/binary_writer.hpp | 213 +++-- .../nlohmann/detail/output/error_handler.hpp | 50 ++ include/nlohmann/detail/output/serializer.hpp | 72 +- include/nlohmann/detail/string_utils.hpp | 130 ++++ include/nlohmann/json.hpp | 135 ++-- single_include/nlohmann/json.hpp | 734 ++++++++++++++---- tests/src/unit-binary_utf8_error_handler.cpp | 362 +++++++++ tests/src/unit-binary_utf8_strict.cpp | 16 +- 30 files changed, 1794 insertions(+), 422 deletions(-) create mode 100644 include/nlohmann/detail/output/error_handler.hpp create mode 100644 tests/src/unit-binary_utf8_error_handler.cpp diff --git a/docs/mkdocs/docs/api/basic_json/dump.md b/docs/mkdocs/docs/api/basic_json/dump.md index a3c9db3a8..d805f3db1 100644 --- a/docs/mkdocs/docs/api/basic_json/dump.md +++ b/docs/mkdocs/docs/api/basic_json/dump.md @@ -25,10 +25,12 @@ and `ensure_ascii` parameters. result consists of ASCII characters only. `error_handler` (in) -: how to react on decoding errors; there are three possible values (see [`error_handler_t`](error_handler_t.md): +: how to react on decoding errors; there are four possible values (see [`error_handler_t`](error_handler_t.md): `strict` (throws an exception in case a decoding error occurs; default), `replace` (replace invalid UTF-8 sequences - with U+FFFD), and `ignore` (ignore invalid UTF-8 sequences during serialization; all valid bytes are copied to the - output unchanged, and invalid bytes are dropped)). + with U+FFFD), `ignore` (ignore invalid UTF-8 sequences during serialization; all valid bytes are copied to the + output unchanged, and invalid bytes are dropped), and `keep` (write the ill-formed bytes to the output as is, + without escaping them, even if `ensure_ascii` is `#!cpp true`; the result is then not valid UTF-8, but equals the + input bytes exactly, and well-formed characters around the ill-formed bytes are still escaped as usual)). ## Return value @@ -94,3 +96,4 @@ Binary values are serialized as an object containing two keys: - Indentation character `indent_char`, option `ensure_ascii` and exceptions added in version 3.0.0. - Error handlers added in version 3.4.0. - Serialization of binary values added in version 3.8.0. +- Error handler `keep` added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/error_handler_t.md b/docs/mkdocs/docs/api/basic_json/error_handler_t.md index 51dc6510f..17327fe26 100644 --- a/docs/mkdocs/docs/api/basic_json/error_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/error_handler_t.md @@ -4,15 +4,31 @@ enum class error_handler_t { strict, replace, - ignore + ignore, + keep }; ``` -This enumeration is used in the [`dump`](dump.md) function to choose how to treat decoding errors while serializing a -`basic_json` value. Three values are differentiated: +This enumeration is used to choose how to treat ill-formed UTF-8 in a string value or object key: + +- [`dump`](dump.md) uses it while serializing a `basic_json` value to text. +- [`to_cbor`](to_cbor.md), [`to_msgpack`](to_msgpack.md), [`to_ubjson`](to_ubjson.md), [`to_bjdata`](to_bjdata.md), + and [`to_bson`](to_bson.md) use it while serializing a `basic_json` value to that binary format. Their default is + `keep`, as no binary writer checked before this parameter was added. CBOR, UBJSON, BJData, and BSON require valid + UTF-8, so for these four the default is `strict` if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) + is enabled; MessagePack's specification explicitly allows a string to contain ill-formed UTF-8, so `to_msgpack` + stays at `keep`. `to_bon8` does not take this parameter: BON8 always validates, since UTF-8 lead bytes are + structural to that format. +- [`from_cbor`](from_cbor.md), [`from_msgpack`](from_msgpack.md), [`from_ubjson`](from_ubjson.md), + [`from_bjdata`](from_bjdata.md), and [`from_bson`](from_bson.md) use it while parsing that binary format, to decide + whether to check a string value or object key for well-formed UTF-8 at all; by default (`keep`) they do not, as no + binary reader did before this parameter was added. `from_bon8` does not take this parameter, for the same reason + `to_bon8` does not. + +Four values are differentiated: strict -: throw a `type_error` exception in case of invalid UTF-8 +: throw a `type_error`/`parse_error` exception in case of invalid UTF-8 replace : replace invalid UTF-8 sequences with U+FFFD (ļæ½ REPLACEMENT CHARACTER) @@ -20,6 +36,12 @@ replace ignore : ignore invalid UTF-8 sequences; all valid bytes are copied to the output unchanged, and invalid bytes are dropped +keep +: keep invalid UTF-8 sequences unchanged; only meaningful for the binary formats mentioned above, since [`dump`] + (dump.md) itself must produce text, and `keep` there writes the ill-formed bytes to the output as is, so the + result is then not valid UTF-8 (but still equals the input bytes exactly, including around any well-formed + characters, which are still escaped as usual) + ## Examples ??? example @@ -45,3 +67,5 @@ ignore ## Version history - Added in version 3.4.0. +- Added `keep`, and made this enumeration apply to the binary readers and writers in addition to `dump`, in version + 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/from_bjdata.md b/docs/mkdocs/docs/api/basic_json/from_bjdata.md index 9df21ab30..a45e00ad5 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -5,12 +5,14 @@ template static basic_json from_bjdata(InputType&& i, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); // (2) template static basic_json from_bjdata(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); ``` Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format. @@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `allow_exceptions` (in) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) +`error_handler` (in) +: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + BJData does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not + check at all, as every binary reader did before this parameter was added; `strict` checks and throws; + `replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would + ## Return value deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be @@ -73,7 +81,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va the end of the file was not reached when `strict` was set to true - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed - successfully + successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict` - Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container or n-dimensional array cannot be represented by `std::size_t` @@ -111,3 +119,4 @@ Linear in the size of the input. - Added in version 3.11.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/from_bson.md b/docs/mkdocs/docs/api/basic_json/from_bson.md index 9dfd9dc18..b037e8e07 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bson.md +++ b/docs/mkdocs/docs/api/basic_json/from_bson.md @@ -5,12 +5,14 @@ template static basic_json from_bson(InputType&& i, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); // (2) template static basic_json from_bson(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); ``` Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format. @@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `allow_exceptions` (in) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) +`error_handler` (in) +: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + BSON does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not + check at all, as every binary reader did before this parameter was added; `strict` checks and throws; + `replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would + ## Return value deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be @@ -75,6 +83,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va invalid string or byte array length) - Throws [`parse_error.114`](../../home/exceptions.md#jsonexceptionparse_error114) if an unsupported BSON record type is encountered +- Throws [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) if a string value or object key is + not valid UTF-8 and `error_handler` is `strict` ## Complexity @@ -111,6 +121,7 @@ Linear in the size of the input. - Added in version 3.4.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/from_cbor.md b/docs/mkdocs/docs/api/basic_json/from_cbor.md index 791c183bb..7d7df08a5 100644 --- a/docs/mkdocs/docs/api/basic_json/from_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/from_cbor.md @@ -6,14 +6,16 @@ template static basic_json from_cbor(InputType&& i, const bool strict = true, const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error, + const error_handler_t error_handler = error_handler_t::keep); // (2) template static basic_json from_cbor(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error, + const error_handler_t error_handler = error_handler_t::keep); ``` Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. @@ -65,6 +67,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : how to treat CBOR tags (optional, `error` by default); see [`cbor_tag_handler_t`](cbor_tag_handler_t.md) for more information +`error_handler` (in) +: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + CBOR does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not + check at all, as every binary reader did before this parameter was added; `strict` checks and throws; + `replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would + ## Return value deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be @@ -80,8 +88,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va the end of the file was not reached when `strict` was set to true - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were used in the given input or if the input is not valid CBOR -- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other - types are not supported, as JSON object keys are always strings) or a string is malformed +- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of + other types are not supported, as JSON object keys are always strings), or if a string value or object key is not + valid UTF-8 and `error_handler` is `strict` ## Complexity @@ -121,6 +130,7 @@ Linear in the size of the input. - Added `tag_handler` parameter in version 3.9.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/from_msgpack.md b/docs/mkdocs/docs/api/basic_json/from_msgpack.md index 395512acb..2e7fa5062 100644 --- a/docs/mkdocs/docs/api/basic_json/from_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/from_msgpack.md @@ -5,12 +5,14 @@ template static basic_json from_msgpack(InputType&& i, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); // (2) template static basic_json from_msgpack(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); ``` Deserializes a given input to a JSON value using the MessagePack serialization format. @@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `allow_exceptions` (in) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) +`error_handler` (in) +: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + MessagePack's specification explicitly allows ill-formed UTF-8, so checking is opt-in: the default, `keep`, does + not check at all, as every binary reader did before this parameter was added; `strict` checks and throws; + `replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would + ## Return value deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be @@ -73,8 +81,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va the end of the file was not reached when `strict` was set to true - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from MessagePack were used in the given input or if the input is not valid MessagePack -- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other - types are not supported, as JSON object keys are always strings) or a string is malformed +- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of + other types are not supported, as JSON object keys are always strings), or if a string value or object key is not + valid UTF-8 and `error_handler` is `strict` ## Complexity @@ -113,6 +122,7 @@ Linear in the size of the input. - Added `allow_exceptions` parameter in version 3.2.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/from_ubjson.md b/docs/mkdocs/docs/api/basic_json/from_ubjson.md index 1ad076588..ac9404d4c 100644 --- a/docs/mkdocs/docs/api/basic_json/from_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/from_ubjson.md @@ -5,12 +5,14 @@ template static basic_json from_ubjson(InputType&& i, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); // (2) template static basic_json from_ubjson(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true); + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep); ``` Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format. @@ -58,6 +60,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `allow_exceptions` (in) : whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default) +`error_handler` (in) +: how to treat a string value or object key that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + UBJSON does not require a decoder to reject ill-formed UTF-8, so checking is opt-in: the default, `keep`, does not + check at all, as every binary reader did before this parameter was added; `strict` checks and throws; + `replace`/`ignore` sanitize the string the same way [`dump`](dump.md) would + ## Return value deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be @@ -73,7 +81,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va the end of the file was not reached when `strict` was set to true - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed - successfully + successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict` - Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container or n-dimensional array cannot be represented by `std::size_t` @@ -112,6 +120,7 @@ Linear in the size of the input. - Added `allow_exceptions` parameter in version 3.2.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. !!! warning "Deprecation" diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index 63dc5379e..df3b69004 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -5,15 +5,18 @@ static std::vector to_bjdata(const basic_json& j, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2); + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = error_handler_t::keep); // (2) static void to_bjdata(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2); + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = error_handler_t::keep); static void to_bjdata(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2); + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = error_handler_t::keep); ``` Serializes a given JSON value `j` to a byte vector using the BJData (Binary JData) serialization format. BJData aims to @@ -43,6 +46,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : which version of BJData to use (see note on "Binary values" on [BJData](../../features/binary_formats/bjdata.md)); optional, `#!cpp bjdata_version_t::draft2` by default. +`error_handler` (in) +: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_bjdata` did before + this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would. + If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead. + ## Return value 1. BJData serialization as byte vector @@ -56,9 +65,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` is false, and `j` contains a non-empty array, object, or binary value. -- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not - valid UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are - written unchanged +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is + not valid UTF-8 and `error_handler` is `strict` (the default only if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) ## Complexity @@ -108,5 +117,6 @@ Linear in the size of the JSON value `j`. - Added in version 3.11.0. - BJData version parameter (for draft3 binary encoding) added in version 3.12.0. -- Throwing `type_error.316` for a string or object key that is not valid UTF-8 if - [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled added in version 3.13.0. \ No newline at end of file +- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key + that is not valid UTF-8 unchanged, as before; `strict` (the default if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. \ No newline at end of file diff --git a/docs/mkdocs/docs/api/basic_json/to_bson.md b/docs/mkdocs/docs/api/basic_json/to_bson.md index e3104b13c..ad974e048 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bson.md +++ b/docs/mkdocs/docs/api/basic_json/to_bson.md @@ -2,11 +2,14 @@ ```cpp // (1) -static std::vector to_bson(const basic_json& j); +static std::vector to_bson(const basic_json& j, + const error_handler_t error_handler = error_handler_t::keep); // (2) -static void to_bson(const basic_json& j, detail::output_adapter o); -static void to_bson(const basic_json& j, detail::output_adapter o); +static void to_bson(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep); +static void to_bson(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep); ``` BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a @@ -25,6 +28,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `o` (in) : output adapter to write serialization to +`error_handler` (in) +: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_bson` did before + this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would. + If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead. + ## Return value 1. BSON serialization as a byte vector @@ -46,9 +55,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value exceeds 255, the maximum of the BSON binary subtype; example: `"subtype 70000 is too large for the BSON binary subtype (max 255)"` -- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is not valid - UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are - written unchanged +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is + not valid UTF-8 and `error_handler` is `strict` (the default only if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) ## Complexity @@ -101,6 +110,7 @@ pass before anything is written. - Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. - Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0. - `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0. -- Throwing `type_error.316` for a string value or object key that is not valid UTF-8 if - [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, detected before anything is written, - added in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key + that is not valid UTF-8 unchanged, as before; `strict` (the default if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything + is written. diff --git a/docs/mkdocs/docs/api/basic_json/to_cbor.md b/docs/mkdocs/docs/api/basic_json/to_cbor.md index cac8a0917..c72f310bb 100644 --- a/docs/mkdocs/docs/api/basic_json/to_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/to_cbor.md @@ -2,11 +2,14 @@ ```cpp // (1) -static std::vector to_cbor(const basic_json& j); +static std::vector to_cbor(const basic_json& j, + const error_handler_t error_handler = error_handler_t::keep); // (2) -static void to_cbor(const basic_json& j, detail::output_adapter o); -static void to_cbor(const basic_json& j, detail::output_adapter o); +static void to_cbor(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep); +static void to_cbor(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep); ``` Serializes a given JSON value `j` to a byte vector using the CBOR (Concise Binary Object Representation) serialization @@ -26,6 +29,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `o` (in) : output adapter to write serialization to +`error_handler` (in) +: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_cbor` did before + this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would. + If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead. + ## Return value 1. CBOR serialization as a byte vector @@ -37,9 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va ## Exceptions -- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not - valid UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are - written unchanged +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is + not valid UTF-8 and `error_handler` is `strict` (the default only if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) ## Complexity @@ -74,5 +83,6 @@ Linear in the size of the JSON value `j`. - Added in version 2.0.9. - Compact representation of floating-point numbers added in version 3.8.0. -- Throwing `type_error.316` for a string or object key that is not valid UTF-8 if - [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled added in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key + that is not valid UTF-8 unchanged, as before; `strict` (the default if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. diff --git a/docs/mkdocs/docs/api/basic_json/to_msgpack.md b/docs/mkdocs/docs/api/basic_json/to_msgpack.md index 707de9514..17b9fe29c 100644 --- a/docs/mkdocs/docs/api/basic_json/to_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/to_msgpack.md @@ -2,11 +2,14 @@ ```cpp // (1) -static std::vector to_msgpack(const basic_json& j); +static std::vector to_msgpack(const basic_json& j, + const error_handler_t error_handler = error_handler_t::keep); // (2) -static void to_msgpack(const basic_json& j, detail::output_adapter o); -static void to_msgpack(const basic_json& j, detail::output_adapter o); +static void to_msgpack(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep); +static void to_msgpack(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep); ``` Serializes a given JSON value `j` to a byte vector using the MessagePack serialization format. MessagePack is a binary @@ -25,6 +28,13 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `o` (in) : output adapter to write serialization to +`error_handler` (in) +: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_msgpack` did before + this parameter was added and as the MessagePack specification allows; `strict` throws; `replace`/`ignore` sanitize + it the same way [`dump`](dump.md) would. Unlike the other binary writers, the default stays `keep` even if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled. + ## Return value 1. MessagePack serialization as a byte vector @@ -42,6 +52,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value exceeds 255, the maximum of the MessagePack ext type; example: `"subtype 70000 is too large for the MessagePack ext type (max 255)"` +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is + not valid UTF-8 and `error_handler` is `strict` ## Complexity @@ -91,6 +103,8 @@ Linear in the size of the JSON value `j`. - Added in version 2.0.9. - Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key + that is not valid UTF-8 unchanged, as before. - Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly; before, integers could be serialized with the wrong value if `number_integer_t` was narrower than `number_unsigned_t`. diff --git a/docs/mkdocs/docs/api/basic_json/to_ubjson.md b/docs/mkdocs/docs/api/basic_json/to_ubjson.md index 8f2ea5eb9..6860c01c6 100644 --- a/docs/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/to_ubjson.md @@ -4,13 +4,16 @@ // (1) static std::vector to_ubjson(const basic_json& j, const bool use_size = false, - const bool use_type = false); + const bool use_type = false, + const error_handler_t error_handler = error_handler_t::keep); // (2) static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false); + const bool use_size = false, const bool use_type = false, + const error_handler_t error_handler = error_handler_t::keep); static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false); + const bool use_size = false, const bool use_type = false, + const error_handler_t error_handler = error_handler_t::keep); ``` Serializes a given JSON value `j` to a byte vector using the UBJSON (Universal Binary JSON) serialization format. UBJSON @@ -36,6 +39,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../ : whether to add type annotations to container types (must be combined with `#!cpp use_size = true`); optional, `#!cpp false` by default. +`error_handler` (in) +: how to treat a string or object key in `j` that is not valid UTF-8; see [`error_handler_t`](error_handler_t.md). + The default, `keep`, writes the ill-formed bytes to the output as is, as every version of `to_ubjson` did before + this parameter was added; `strict` throws; `replace`/`ignore` sanitize it the same way [`dump`](dump.md) would. + If [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled, the default is `strict` instead. + ## Return value 1. UBJSON serialization as a byte vector @@ -49,9 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` is false, and `j` contains a non-empty array, object, or binary value. -- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not - valid UTF-8 and [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled; otherwise, the bytes are - written unchanged +- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is + not valid UTF-8 and `error_handler` is `strict` (the default only if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) ## Complexity @@ -100,5 +109,6 @@ Linear in the size of the JSON value `j`. ## Version history - Added in version 3.1.0. -- Throwing `type_error.316` for a string or object key that is not valid UTF-8 if - [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled added in version 3.13.0. +- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key + that is not valid UTF-8 unchanged, as before; `strict` (the default if + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. diff --git a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md index 7fe6d9a58..ed7bbe6b3 100644 --- a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md +++ b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md @@ -4,15 +4,18 @@ #define JSON_STRICT_BINARY_UTF8 /* value */ ``` -When defined to `1`, the binary writers [`to_cbor`](../basic_json/to_cbor.md), [`to_ubjson`](../basic_json/to_ubjson.md), -[`to_bjdata`](../basic_json/to_bjdata.md), and [`to_bson`](../basic_json/to_bson.md) check every string value and -object key for valid UTF-8 and throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for -ill-formed UTF-8, like [`dump`](../basic_json/dump.md) does. Without it, they write the bytes unchanged. +When defined to `1`, the `error_handler` parameter of the binary writers [`to_cbor`](../basic_json/to_cbor.md), +[`to_ubjson`](../basic_json/to_ubjson.md), [`to_bjdata`](../basic_json/to_bjdata.md), and +[`to_bson`](../basic_json/to_bson.md) defaults to [`error_handler_t::strict`](../basic_json/error_handler_t.md) instead +of `error_handler_t::keep`. These writers then check every string value and object key for valid UTF-8 and throw +[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, like +[`dump`](../basic_json/dump.md) does. Without it, they write the bytes unchanged. An `error_handler` passed explicitly +always takes precedence. The macro does not affect: - [`to_msgpack`](../basic_json/to_msgpack.md): the MessagePack specification allows a `str` value to contain bytes that - are not valid UTF-8, so it always writes them unchanged. + are not valid UTF-8, so its `error_handler` always defaults to `keep`. - [`to_bon8`](../basic_json/to_bon8.md): BON8 always checks, because the UTF-8 lead bytes mark where a string ends. - The binary readers ([`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md), [`from_ubjson`](../basic_json/from_ubjson.md), [`from_bjdata`](../basic_json/from_bjdata.md), @@ -33,8 +36,9 @@ The default value is `0` (disabled, the behavior of version 3.12.0 and earlier i CBOR, UBJSON, BJData, and BSON all require strings to be UTF-8. Up to version 3.12.0, the writers did not check this, so they could produce output that other decoders reject. Checking by default would break code that stores - other encodings (for instance ISO 8859-1) in a string and only ever writes it to a binary format, so this macro - offers the check as an opt-in ahead of version 4.0.0, where it is planned to become the default (see + other encodings (for instance ISO 8859-1) in a string and only ever writes it to a binary format. You can pass + `error_handler_t::strict` to each call, or use this macro to check by default ahead of version 4.0.0, where + `strict` is planned to become the default (see [#5529](https://github.com/nlohmann/json/issues/5529) and [#5651](https://github.com/nlohmann/json/issues/5651)). !!! warning "Opt-in only" diff --git a/docs/mkdocs/docs/examples/error_handler_t.cpp b/docs/mkdocs/docs/examples/error_handler_t.cpp index b4718d7e6..bf035cea3 100644 --- a/docs/mkdocs/docs/examples/error_handler_t.cpp +++ b/docs/mkdocs/docs/examples/error_handler_t.cpp @@ -20,5 +20,6 @@ int main() << j_invalid.dump(-1, ' ', false, json::error_handler_t::replace) << "\nstring with ignored invalid characters: " << j_invalid.dump(-1, ' ', false, json::error_handler_t::ignore) - << '\n'; + << "\nstring with the invalid byte kept as is (" << j_invalid.dump(-1, ' ', false, json::error_handler_t::keep).size() + << " bytes, not valid UTF-8 itself)\n"; } diff --git a/docs/mkdocs/docs/examples/error_handler_t.output b/docs/mkdocs/docs/examples/error_handler_t.output index 718d62bee..37cae62a2 100644 --- a/docs/mkdocs/docs/examples/error_handler_t.output +++ b/docs/mkdocs/docs/examples/error_handler_t.output @@ -1,3 +1,4 @@ [json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9 string with replaced invalid characters: "ä�ü" string with ignored invalid characters: "äü" +string with the invalid byte kept as is (7 bytes, not valid UTF-8 itself) diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md index 12d1af51b..3e3d8e885 100644 --- a/docs/mkdocs/docs/features/binary_formats/bjdata.md +++ b/docs/mkdocs/docs/features/binary_formats/bjdata.md @@ -65,10 +65,12 @@ The library uses the following mapping from JSON values types to BJData types ac !!! warning "UTF-8 validation of string values and object keys" - BJData strings must use UTF-8 encoding. By default, `to_bjdata()` writes the bytes of string values and object keys - unchanged, even if they are not valid UTF-8. If - [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it throws - [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead. + BJData strings must use UTF-8 encoding. By default (the [`error_handler`](../../api/basic_json/to_bjdata.md) + parameter left at `keep`), `to_bjdata()` writes the bytes of string values and object keys unchanged, even if they + are not valid UTF-8. With `error_handler_t::strict`, it throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead; + `replace`/`ignore` sanitize the string. [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) + makes `strict` the default. !!! info "Unused BJData markers" @@ -217,12 +219,16 @@ The library maps BJData types to JSON value types as follows: !!! warning "Ill-formed UTF-8 in string values and object keys" - BJData strings must use UTF-8 encoding, but this is not enforced on read: `from_bjdata()` accepts a string - value or object key whose bytes are not valid UTF-8 and hands them back unchanged. However, + BJData strings must use UTF-8 encoding, but checking it on read is opt-in: with the + [`error_handler`](../../api/basic_json/from_bjdata.md) parameter left at `keep` (the default), `from_bjdata()` + accepts a string value or object key whose bytes are not valid UTF-8 and hands them back unchanged. Passing + `error_handler_t::strict` makes `from_bjdata()` check and throw + [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and + `replace`/`ignore` sanitize the string instead of keeping it. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws - [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error - handler is passed that replaces or ignores the ill-formed bytes. By default, `to_bjdata()` writes such a value - back unchanged (see above). + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read with the default + `keep` handler, unless an error handler is passed that replaces or ignores the ill-formed bytes. `to_bjdata()`'s + own `error_handler` parameter defaults to `keep` (see above), so such a value is written back unchanged. !!! info "Round trips" diff --git a/docs/mkdocs/docs/features/binary_formats/bson.md b/docs/mkdocs/docs/features/binary_formats/bson.md index 245ed3ebf..f205cba17 100644 --- a/docs/mkdocs/docs/features/binary_formats/bson.md +++ b/docs/mkdocs/docs/features/binary_formats/bson.md @@ -112,13 +112,18 @@ The library maps BSON record types to JSON value types as follows: !!! warning "Ill-formed UTF-8 in string values" The BSON specification requires `string` values (type `0x02`) to be valid UTF-8, but this is not required of a - decoder. `from_bson()` accepts a `string` value whose bytes are not valid UTF-8 and hands them back unchanged. - However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws - [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error handler is - passed that replaces or ignores the ill-formed bytes. By default, `to_bson()` writes such a string value or element - (key) name unchanged; if [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it - throws the same exception instead. Element (key) names are never validated on read, since they are read byte-by-byte - as a C string. `binary` values (type `0x05`) are unaffected, since they are not required to hold text. + decoder, so checking is opt-in: with the [`error_handler`](../../api/basic_json/from_bson.md) parameter left at + `keep` (the default), `from_bson()` accepts a `string` value whose bytes are not valid UTF-8 and hands them back + unchanged. Passing `error_handler_t::strict` makes `from_bson()` check and throw + [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and + `replace`/`ignore` sanitize the string instead of keeping it. However, [`dump()`](../../api/basic_json/dump.md) + still requires valid UTF-8 and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a + value read with the default `keep` handler, unless an error handler is passed that replaces or ignores the + ill-formed bytes. `to_bson()`'s own `error_handler` parameter defaults to `keep`, so such a string value or element + (key) name is written unchanged; with `strict` (the default if + [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception + instead. Element (key) names are never validated on read, since they are read byte-by-byte as a C string. `binary` + values (type `0x05`) are unaffected, since they are not required to hold text. ??? example "Example: deserialize a JSON value from BSON" diff --git a/docs/mkdocs/docs/features/binary_formats/cbor.md b/docs/mkdocs/docs/features/binary_formats/cbor.md index 66300c5fa..7b5be4631 100644 --- a/docs/mkdocs/docs/features/binary_formats/cbor.md +++ b/docs/mkdocs/docs/features/binary_formats/cbor.md @@ -192,12 +192,17 @@ The library maps CBOR types to JSON value types as follows: !!! warning "Ill-formed UTF-8 in text strings" [RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major - type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this. This library does not: - `from_cbor()` accepts a text string (object keys included) whose bytes are not valid UTF-8 and hands them back - unchanged. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws - [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error handler is - passed that replaces or ignores the ill-formed bytes. By default, `to_cbor()` writes such a value back unchanged; if - [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it throws the same exception + type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this, so checking is opt-in: with the + [`error_handler`](../../api/basic_json/from_cbor.md) parameter left at `keep` (the default), `from_cbor()` accepts a + text string (object keys included) whose bytes are not valid UTF-8 and hands them back unchanged. Passing + `error_handler_t::strict` makes `from_cbor()` check and throw + [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and + `replace`/`ignore` sanitize the string instead of keeping it. However, [`dump()`](../../api/basic_json/dump.md) + still requires valid UTF-8 and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a + value read with the default `keep` handler, unless an error handler is passed that replaces or ignores the + ill-formed bytes. `to_cbor()`'s own [`error_handler`](../../api/basic_json/to_cbor.md) parameter defaults to `keep`, + so such a value is written back unchanged; with `strict` (the default if + [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception instead. Byte strings (major type 2) are unaffected, since they are not required to hold text. !!! warning "Tagged items" diff --git a/docs/mkdocs/docs/features/binary_formats/messagepack.md b/docs/mkdocs/docs/features/binary_formats/messagepack.md index 047944852..24eeab139 100644 --- a/docs/mkdocs/docs/features/binary_formats/messagepack.md +++ b/docs/mkdocs/docs/features/binary_formats/messagepack.md @@ -157,11 +157,19 @@ The library maps MessagePack types to JSON value types as follows: The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged. - This library follows that: `from_msgpack()` reads `str` bytes (object keys included) as-is, without validating - them, and `to_msgpack()` writes them back as-is, so such a value round-trips through `from_msgpack(to_msgpack(j))` - byte for byte. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws - [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read this way, unless an - error handler is passed that replaces or ignores the ill-formed bytes. + This library follows that by default: with its + [`error_handler`](../../api/basic_json/from_msgpack.md) parameter left at `keep` (the default), + `from_msgpack()` reads `str` bytes (object keys included) as-is, without validating them, so such a value + round-trips through `from_msgpack(to_msgpack(j))` byte for byte. Passing `error_handler_t::strict` makes + `from_msgpack()` check anyway and throw + [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and + `replace`/`ignore` sanitize the string instead of keeping it. `to_msgpack()` also writes `str` bytes as-is by + default, since the specification permits it; its [`error_handler`](../../api/basic_json/to_msgpack.md) parameter + can be set to `strict` to throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) instead, or + to `replace`/`ignore` to sanitize the string, for instance for a decoder that rejects ill-formed UTF-8. However, + [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read this way with the + default `keep` handler, unless an error handler is passed that replaces or ignores the ill-formed bytes. ??? example "Example: deserialize a JSON value from MessagePack" diff --git a/docs/mkdocs/docs/features/binary_formats/ubjson.md b/docs/mkdocs/docs/features/binary_formats/ubjson.md index dbdff6e6c..f7a14d855 100644 --- a/docs/mkdocs/docs/features/binary_formats/ubjson.md +++ b/docs/mkdocs/docs/features/binary_formats/ubjson.md @@ -49,10 +49,12 @@ The library uses the following mapping from JSON values types to UBJSON types ac !!! warning "UTF-8 validation of string values and object keys" - UBJSON's required string encoding is UTF-8. By default, `to_ubjson()` writes the bytes of string values and object - keys unchanged, even if they are not valid UTF-8. If - [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled, it throws - [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead. + UBJSON's required string encoding is UTF-8. By default (the [`error_handler`](../../api/basic_json/to_ubjson.md) + parameter left at `keep`), `to_ubjson()` writes the bytes of string values and object keys unchanged, even if they + are not valid UTF-8. With `error_handler_t::strict`, it throws + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 instead; + `replace`/`ignore` sanitize the string. [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) + makes `strict` the default. !!! info "Unused UBJSON markers" @@ -129,12 +131,16 @@ The library maps UBJSON types to JSON value types as follows: !!! warning "Ill-formed UTF-8 in string values and object keys" - UBJSON's required string encoding is UTF-8, but this is not enforced on read: `from_ubjson()` accepts a string - value or object key whose bytes are not valid UTF-8 and hands them back unchanged. However, + UBJSON's required string encoding is UTF-8, but checking it on read is opt-in: with the + [`error_handler`](../../api/basic_json/from_ubjson.md) parameter left at `keep` (the default), `from_ubjson()` + accepts a string value or object key whose bytes are not valid UTF-8 and hands them back unchanged. Passing + `error_handler_t::strict` makes `from_ubjson()` check and throw + [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) for ill-formed UTF-8, and + `replace`/`ignore` sanitize the string instead of keeping it. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws - [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for such a value, unless an error - handler is passed that replaces or ignores the ill-formed bytes. By default, `to_ubjson()` writes such a value - back unchanged (see above). + [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read with the default + `keep` handler, unless an error handler is passed that replaces or ignores the ill-formed bytes. `to_ubjson()`'s + own `error_handler` parameter defaults to `keep` (see above), so such a value is written back unchanged. ??? example "Example: deserialize a JSON value from UBJSON" diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index a3797cf04..e7eb8fd03 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -340,9 +340,11 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde ### json.exception.parse_error.113 A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a -string was read where one was required (for instance as a map key), or the string's length specification is invalid. -The bytes of a string itself are not checked for valid UTF-8 on read; see the ill-formed UTF-8 notes on the -individual [binary format](../features/binary_formats/index.md) pages for how such a string is handled afterward. +string was read where one was required (for instance as a map key), the string's length specification is invalid, or +the string's bytes are not valid UTF-8 and the `error_handler` parameter of the corresponding `from_*` function is +set to `strict`. By default (`error_handler_t::keep`), the bytes of a string are not checked for valid UTF-8 on read; +see the ill-formed UTF-8 notes on the individual [binary format](../features/binary_formats/index.md) pages for how +such a string is handled depending on `error_handler`. CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other type (for instance integers or `null`) are therefore not supported; see the notes on @@ -365,6 +367,9 @@ type (for instance integers or `null`) are therefore not supported; see the note ``` [json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative ``` + ``` + [json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte + ``` ### json.exception.parse_error.114 @@ -747,10 +752,11 @@ The [`unflatten()`](../api/basic_json/unflatten.md) function only works for an o The [`dump()`](../api/basic_json/dump.md) function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. See the FAQ entry on [serializing untrusted or invalid UTF-8](faq.md#serializing-untrusted-or-invalid-utf-8) for background and the recommended fix. -If [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) is enabled, the binary writers -[`to_cbor()`](../api/basic_json/to_cbor.md), [`to_ubjson()`](../api/basic_json/to_ubjson.md), +The binary writers [`to_cbor()`](../api/basic_json/to_cbor.md), [`to_ubjson()`](../api/basic_json/to_ubjson.md), [`to_bjdata()`](../api/basic_json/to_bjdata.md), and [`to_bson()`](../api/basic_json/to_bson.md) throw this exception -for a string value or object key that is not valid UTF-8 as well. +as well for a string value or object key that is not valid UTF-8 if their `error_handler` is `strict` (the default if +[`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) is enabled). So does +[`to_msgpack()`](../api/basic_json/to_msgpack.md) if `error_handler_t::strict` is passed. !!! failure "Example message" diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 3d79ad41b..1a78a6502 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -108,8 +109,16 @@ class binary_reader @brief create a binary reader @param[in] adapter input adapter to read from + @param[in] format the binary format to parse + @param[in] error_handler how to treat text strings and object keys that + are not well-formed UTF-8; none of the supported formats + requires a decoder to reject those, so the default is to + @ref error_handler_t::keep them unchanged, as every binary + reader did before this parameter existed */ - explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format) + explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json, + const error_handler_t error_handler = error_handler_t::keep) noexcept + : ia(std::move(adapter)), input_format(format), error_handler(error_handler) { (void)detail::is_sax_static_asserts {}; } @@ -428,7 +437,7 @@ class binary_reader { if (get_bson_cstr_bulk(result, std::integral_constant {})) { - return true; + return check_string_utf8(result, "key"); } auto out = std::back_inserter(result); @@ -441,7 +450,7 @@ class binary_reader } if (current == 0x00) { - return true; + return check_string_utf8(result, "key"); } *out++ = static_cast(current); } @@ -522,7 +531,7 @@ class binary_reader "string"), nullptr)); } - return true; + return check_string_utf8(result, "string"); } /*! @@ -1149,7 +1158,7 @@ class binary_reader @return whether string creation completed */ - bool get_cbor_string(string_t& result) + bool get_cbor_string(string_t& result, const char* context = "string") { // number of indefinite-length strings that have been opened and not // closed yet. RFC 8949, Section 3.2.3 does not permit nesting them, @@ -1179,7 +1188,7 @@ class binary_reader { if (--open == 0) { - return true; + return check_string_utf8(result, context); } get(); continue; @@ -1192,7 +1201,7 @@ class binary_reader if (open == 0) { - return true; + return check_string_utf8(result, context); } get(); @@ -1216,7 +1225,7 @@ class binary_reader // EOF and major type 3 (text string) are left to get_cbor_string if (current == char_traits::eof() || (static_cast(current) & 0xE0u) == 0x60u) { - return get_cbor_string(result); + return get_cbor_string(result, "key"); } const char* found = nullptr; @@ -2004,7 +2013,7 @@ class binary_reader @return whether string creation completed */ - bool get_msgpack_string(string_t& result) + bool get_msgpack_string(string_t& result, const char* context = "string") { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) { @@ -2047,25 +2056,25 @@ class binary_reader case 0xBE: case 0xBF: { - return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); + return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result) && check_string_utf8(result, context); } case 0xD9: // str 8 { std::uint8_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context); } case 0xDA: // str 16 { std::uint16_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context); } case 0xDB: // str 32 { std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context); } default: @@ -2143,7 +2152,7 @@ class binary_reader // byte 0xC1 are left to get_msgpack_string if (current == char_traits::eof()) { - return get_msgpack_string(result); + return get_msgpack_string(result, "key"); } if (current <= 0x7F || current >= 0xE0) { @@ -2159,7 +2168,7 @@ class binary_reader } else { - return get_msgpack_string(result); + return get_msgpack_string(result, "key"); } break; } @@ -2405,7 +2414,7 @@ class binary_reader if (top.is_object) { key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, true, "key") || !sax->key(key))) { return false; } @@ -2427,7 +2436,7 @@ class binary_reader if (top.is_object) { key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false, "key") || !sax->key(key))) { return false; } @@ -2495,7 +2504,7 @@ class binary_reader @return whether string creation completed */ - bool get_ubjson_string(string_t& result, const bool get_char = true) + bool get_ubjson_string(string_t& result, const bool get_char = true, const char* context = "string") { if (get_char) { @@ -2516,31 +2525,31 @@ class binary_reader case 'U': { std::uint8_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'i': { std::int8_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'I': { std::int16_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'l': { std::int32_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'L': { std::int64_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'u': @@ -2550,7 +2559,7 @@ class binary_reader break; } std::uint16_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'm': @@ -2560,7 +2569,7 @@ class binary_reader break; } std::uint32_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'M': @@ -2570,7 +2579,7 @@ class binary_reader break; } std::uint64_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } default: @@ -4044,15 +4053,53 @@ class binary_reader const NumberType len, string_t& result) { - // Strings are taken as is: none of CBOR (RFC 8949 §3.1 leaves the - // choice to the decoder), MessagePack (whose spec explicitly allows - // a str object to contain an invalid byte sequence), UBJSON, BJData, - // or BSON requires a decoder to reject ill-formed UTF-8. The bytes - // are kept unchanged; dump() and the binary writers are the ones - // that check them and report type_error.316 if they are not valid. + // Strings are taken as is by default: none of CBOR (RFC 8949 §3.1 + // leaves the choice to the decoder), MessagePack (whose spec + // explicitly allows a str object to contain an invalid byte + // sequence), UBJSON, BJData, or BSON requires a decoder to reject + // ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict, + // rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via + // @ref error_handler, applied once the whole string (all chunks of + // an indefinite-length CBOR string included) has been assembled, by + // @ref check_string_utf8 at the call site. return get_bytes(format, len, "string", result); } + /*! + @brief validate a decoded text string (value or object key) against @ref error_handler + + None of the binary formats requires a decoder to reject ill-formed UTF-8 + in a text string (see @ref get_string), so by default + (@ref error_handler_t::keep) this does nothing. A stricter + @ref error_handler opts into the same well-formedness check @ref + serializer::dump_escaped_impl applies when dumping a string: + @ref error_handler_t::strict rejects ill-formed input with + parse_error.113 (honoring `allow_exceptions` via @a sax), while + @ref error_handler_t::replace / @ref error_handler_t::ignore sanitize + @a result in place, using the exact same rules. + + @param[in,out] result the already assembled string to check + @param[in] context further context information (for diagnostics) + @return whether @a result is acceptable (always true for `keep`) + */ + bool check_string_utf8(string_t& result, const char* context) + { + if (error_handler == error_handler_t::keep || is_valid_utf8(result)) + { + return true; + } + + if (error_handler == error_handler_t::strict) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format, "invalid string: ill-formed UTF-8 byte", context), nullptr)); + } + + result = sanitize_utf8(result, error_handler); + return true; + } + /*! @brief create a byte array by reading bytes from the input @@ -4226,6 +4273,9 @@ class binary_reader /// input format const input_format_t input_format = input_format_t::json; + /// how to treat text strings/object keys that are not well-formed UTF-8 + const error_handler_t error_handler = error_handler_t::keep; + /// the SAX parser json_sax_t* sax = nullptr; diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index da5aa0cf4..e74671db2 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -93,8 +94,12 @@ class binary_writer @param[in] sink output sink to write to (a value-type sink such as output_vector_sink, or output_adapter_sink wrapping a type-erased output adapter) + @param[in] error_handler_ how to treat a string value or object key that + is not valid UTF-8 (CBOR, MessagePack, UBJSON, BJData, and BSON; + never consulted by @ref write_bon8) */ - explicit binary_writer(OutputSinkType sink) : oa(std::move(sink)) + explicit binary_writer(OutputSinkType sink, const error_handler_t error_handler_ = binary_writer_default_error_handler()) + : oa(std::move(sink)), error_handler(error_handler_) {} /*! @@ -107,16 +112,20 @@ class binary_writer from one. @param[in] adapter output adapter to write to + @param[in] error_handler_ how to treat a string value or object key that + is not valid UTF-8 (CBOR, MessagePack, UBJSON, BJData, and BSON; + never consulted by @ref write_bon8) */ template < typename SinkType = OutputSinkType, typename std::enable_if < std::is_constructible>::value, int >::type = 0 > - explicit binary_writer(output_adapter_t adapter) : oa(SinkType(std::move(adapter))) + explicit binary_writer(output_adapter_t adapter, const error_handler_t error_handler_ = binary_writer_default_error_handler()) + : oa(SinkType(std::move(adapter))), error_handler(error_handler_) {} /*! @param[in] j JSON value to serialize - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string - value or an object key is not valid UTF-8 + @throw type_error.316 if a string value or an object key is not valid + UTF-8 @throw type_error.317 if @a j is not an object */ void write_bson(const BasicJsonType& j) @@ -147,8 +156,8 @@ class binary_writer /*! @param[in] j JSON value to serialize - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string - value or an object key is not valid UTF-8 + @throw type_error.316 if a string value or an object key is not valid + UTF-8 */ void write_cbor(const BasicJsonType& j) { @@ -215,15 +224,16 @@ class binary_writer case value_t::string: { - check_text_utf8(*j.m_data.m_value.string, j); + string_t storage; + const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage); // step 1: write control byte and the string length - write_cbor_head(0x60, j.m_data.m_value.string->size()); + write_cbor_head(0x60, value.size()); // step 2: write the string oa.write_characters( - reinterpret_cast(j.m_data.m_value.string->data()), - j.m_data.m_value.string->size()); + reinterpret_cast(value.data()), + value.size()); break; } @@ -296,8 +306,14 @@ class binary_writer // el.first is checked here, against the object as // diagnostics context, because write_cbor(el.first) // converts it to a temporary basic_json that would be - // used as the context instead - check_text_utf8(el.first, j); + // used as the context instead; for error_handler_t::keep + // and ::replace/::ignore the recursive write_cbor(el.first) + // call below handles the key like any other string, so no + // separate check is needed here for those + if (error_handler == error_handler_t::strict) + { + check_utf8(el.first, j); + } write_cbor(el.first); write_cbor(el.second); } @@ -445,8 +461,11 @@ class binary_writer case value_t::string: { + string_t storage; + const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage); + // step 1: write control byte and the string length - const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j); + const auto N = to_msgpack_length(value.size(), j); if (N <= 31) { // fixstr @@ -473,8 +492,8 @@ class binary_writer // step 2: write the string oa.write_characters( - reinterpret_cast(j.m_data.m_value.string->data()), - j.m_data.m_value.string->size()); + reinterpret_cast(value.data()), + value.size()); break; } @@ -621,6 +640,13 @@ class binary_writer // step 2: write each element for (const auto& el : *j.m_data.m_value.object) { + // as in write_cbor, el.first is checked here against the + // object as diagnostics context; the recursive call below + // handles keep/replace/ignore like any other string + if (error_handler == error_handler_t::strict) + { + check_utf8(el.first, j); + } write_msgpack(el.first); write_msgpack(el.second); } @@ -640,8 +666,8 @@ class binary_writer @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false @param[in] bjdata_version which BJData version to use, default is draft2 - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string - value or an object key is not valid UTF-8 + @throw type_error.316 if a string value or an object key is not valid + UTF-8 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, @@ -691,16 +717,17 @@ class binary_writer case value_t::string: { - check_text_utf8(*j.m_data.m_value.string, j); + string_t storage; + const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage); if (add_prefix) { oa.write_character(to_char_type('S')); } - write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata); + write_number_with_ubjson_prefix(value.size(), true, use_bjdata); oa.write_characters( - reinterpret_cast(j.m_data.m_value.string->data()), - j.m_data.m_value.string->size()); + reinterpret_cast(value.data()), + value.size()); break; } @@ -855,11 +882,12 @@ class binary_writer for (const auto& el : *j.m_data.m_value.object) { - check_text_utf8(el.first, j); - write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); + string_t storage; + const string_t& key = sanitize_utf8_for_write(el.first, j, storage); + write_number_with_ubjson_prefix(key.size(), true, use_bjdata); oa.write_characters( - reinterpret_cast(el.first.data()), - el.first.size()); + reinterpret_cast(key.data()), + key.size()); write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } @@ -902,10 +930,10 @@ class binary_writer and the entry name size (and its null-terminator). @throw out_of_range.409 if @a name contains U+0000, before anything is written - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a name is - not valid UTF-8, before anything is written + @throw type_error.316 if @a name is not valid UTF-8, before anything is + written */ - static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) + std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) { const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) @@ -913,9 +941,10 @@ class binary_writer JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); } - check_text_utf8(name, j); + string_t storage; + const string_t& sanitized = sanitize_utf8_for_write(name, j, storage); - return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; + return /*id*/ 1ul + sanitized.size() + /*zero-terminator*/1u; } /*! @@ -935,14 +964,28 @@ class binary_writer /*! @brief Writes the given @a element_type and @a name to the output adapter + + @a name has already been validated (and, for @ref error_handler_t::strict, + found well-formed) by @ref calc_bson_entry_header_size during the earlier + size pass, so only @ref error_handler_t::replace / @ref + error_handler_t::ignore need to sanitize it again here, to actually write + the bytes that size was computed from. */ void write_bson_entry_header(const string_t& name, const std::uint8_t element_type) { oa.write_character(to_char_type(element_type)); - oa.write_characters( - reinterpret_cast(name.data()), - name.size()); + + if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(name)) + { + oa.write_characters(reinterpret_cast(name.data()), name.size()); + } + else + { + const string_t sanitized = sanitize_utf8(name, error_handler); + oa.write_characters(reinterpret_cast(sanitized.data()), sanitized.size()); + } + // the terminating null byte is written explicitly rather than taken // from the buffer, so that string_t::data() need not be null-terminated oa.write_character(to_char_type(0x00)); @@ -970,8 +1013,8 @@ class binary_writer /*! @return The size of the BSON-encoded string in @a value - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a value - is not valid UTF-8, before anything is written + @throw type_error.316 if @a value is not valid UTF-8, before anything is + written @note The UTF-8 check is skipped if @a value is already too long for the 32-bit BSON length field (@ref to_bson_length rejects it later, once @@ -979,27 +1022,41 @@ class binary_writer from reading past a StringType that reports a size larger than what it actually holds. */ - static std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j) + std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j) { if (JSON_HEDLEY_LIKELY(value_in_range_of(value.size()))) { - check_text_utf8(value, j); + string_t storage; + const string_t& sanitized = sanitize_utf8_for_write(value, j, storage); + return sizeof(std::int32_t) + sanitized.size() + 1ul; } return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and string value @a value + + @a value has already been validated (and, for @ref error_handler_t::strict, + found well-formed) by @ref calc_bson_string_size during the earlier size + pass, so only @ref error_handler_t::replace / @ref error_handler_t::ignore + need to sanitize it again here, to actually write the bytes that size was + computed from. */ void write_bson_string(const string_t& name, const string_t& value) { write_bson_entry_header(name, 0x02); - write_number(to_bson_length(value.size() + 1ul), true); + const bool sanitize = error_handler != error_handler_t::keep + && error_handler != error_handler_t::strict + && !is_valid_utf8(value); + const string_t sanitized = sanitize ? sanitize_utf8(value, error_handler) : string_t{}; + const string_t& written = sanitize ? sanitized : value; + + write_number(to_bson_length(written.size() + 1ul), true); oa.write_characters( - reinterpret_cast(value.data()), - value.size()); + reinterpret_cast(written.data()), + written.size()); // the terminating null byte is written explicitly rather than taken // from the buffer, so that string_t::data() need not be null-terminated oa.write_character(to_char_type(0x00)); @@ -1113,10 +1170,10 @@ class binary_writer is neither an object nor an array @throw out_of_range.415 if @a j is binary with a subtype that does not fit into a byte, before anything is written - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a j is a - string that is not valid UTF-8, before anything is written + @throw type_error.316 if @a j is a string that is not valid UTF-8, before + anything is written */ - static std::size_t calc_bson_value_size(const BasicJsonType& j) + std::size_t calc_bson_value_size(const BasicJsonType& j) { switch (j.type()) { @@ -1249,10 +1306,10 @@ class binary_writer written @throw out_of_range.415 if a binary value's subtype does not fit into a byte, before anything is written - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and a string - value or a key is not valid UTF-8, before anything is written + @throw type_error.316 if a string value or a key is not valid UTF-8, + before anything is written */ - static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector& nested_sizes) + std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector& nested_sizes) { // the object or array whose entries are being sized, and the ones it // is in; nothing is allocated unless the document nests @@ -2171,26 +2228,54 @@ class binary_writer } /*! - @brief check a CBOR, UBJSON, BJData, or BSON text string for valid UTF-8 + @brief return @a s as it should be written, honoring @ref error_handler - The check only happens if JSON_STRICT_BINARY_UTF8 is enabled. Otherwise, - the bytes are written unchanged, as before version 3.13.0. MessagePack - always writes the bytes as is, and BON8 always checks them (see - @ref check_utf8). + Used by @ref write_cbor, @ref write_msgpack, @ref write_ubjson (and so + @ref write_bjdata), and the BSON writing functions for string values and + object keys; never by @ref write_bon8, which always validates, since UTF-8 + lead bytes are structural there. - @param[in] s the string to check - @param[in] context the value that holds @a s (for diagnostics) - @throw type_error.316 if JSON_STRICT_BINARY_UTF8 is enabled and @a s is - not valid UTF-8 + - @ref error_handler_t::keep: @a s is returned unchanged, without even + checking it (the behavior of release 3.12.0 and earlier). + - @ref error_handler_t::strict: @ref check_utf8 is called, which throws + type_error.316 if @a s is not valid UTF-8. + - @ref error_handler_t::replace / @ref error_handler_t::ignore: @a s is + sanitized into @a storage with exactly the rules @ref + serializer::dump_escaped_impl uses, so that parsing what @ref + basic_json::dump produces for the same string and the same handler + yields the same result. + + Well-formed input is never copied: this returns a reference to @a s + itself in every case but a sanitized `replace`/`ignore` one, so @a + storage must outlive the returned reference only then. + + @param[in] s the string (value or object key) to write + @param[in] context the value @a s belongs to (for diagnostics) + @param[out] storage backing storage for a sanitized copy + + @return a reference to @a s, or to @a storage once it holds a sanitized copy */ - static void check_text_utf8(const string_t& s, const BasicJsonType& context) + const string_t& sanitize_utf8_for_write(const string_t& s, const BasicJsonType& context, string_t& storage) const { -#if JSON_STRICT_BINARY_UTF8 - check_utf8(s, context); -#else - static_cast(s); - static_cast(context); -#endif + switch (error_handler) + { + case error_handler_t::keep: + return s; + + case error_handler_t::strict: + check_utf8(s, context); + return s; + + case error_handler_t::replace: + case error_handler_t::ignore: + default: + if (is_valid_utf8(s)) + { + return s; + } + storage = sanitize_utf8(s, error_handler); + return storage; + } } /*! @@ -2521,6 +2606,10 @@ class binary_writer /// the output OutputSinkType oa; + + /// how to treat a string value or object key that is not valid UTF-8 + /// (CBOR, MessagePack, UBJSON, BJData, and BSON; not BON8) + const error_handler_t error_handler = binary_writer_default_error_handler(); }; } // namespace detail diff --git a/include/nlohmann/detail/output/error_handler.hpp b/include/nlohmann/detail/output/error_handler.hpp new file mode 100644 index 000000000..b95d70fce --- /dev/null +++ b/include/nlohmann/detail/output/error_handler.hpp @@ -0,0 +1,50 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// how to treat decoding errors +/// +/// @ref basic_json::dump uses this to decide what to do with ill-formed +/// UTF-8 while escaping a string, and the binary writers (@ref +/// basic_json::to_cbor, @ref basic_json::to_ubjson, @ref +/// basic_json::to_bjdata, @ref basic_json::to_bson) use it the same way for +/// string values and object keys. The binary readers (@ref +/// basic_json::from_cbor, @ref basic_json::from_msgpack, @ref +/// basic_json::from_ubjson, @ref basic_json::from_bjdata, @ref +/// basic_json::from_bson) use it to decide whether to check text strings +/// and object keys for well-formed UTF-8 at all, since none of those +/// formats requires a decoder to do so. +enum class error_handler_t +{ + strict, ///< throw a type_error/parse_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore, ///< ignore invalid UTF-8 sequences + keep ///< keep invalid UTF-8 sequences unchanged +}; + +/// the default error handler of the CBOR, UBJSON, BJData, and BSON writers: +/// error_handler_t::strict if JSON_STRICT_BINARY_UTF8 is enabled, otherwise +/// error_handler_t::keep (the behavior before version 3.13.0) +constexpr error_handler_t binary_writer_default_error_handler() noexcept +{ +#if JSON_STRICT_BINARY_UTF8 + return error_handler_t::strict; +#else + return error_handler_t::keep; +#endif +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 1c519d4c7..dcaae8055 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -41,14 +42,6 @@ namespace detail // serialization // /////////////////// -/// how to treat decoding errors -enum class error_handler_t -{ - strict, ///< throw a type_error exception in case of invalid UTF-8 - replace, ///< replace invalid UTF-8 sequences with U+FFFD - ignore ///< ignore invalid UTF-8 sequences -}; - template class serializer { @@ -839,6 +832,16 @@ class serializer // EnsureAscii parameter is used, non-ASCII characters if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F))) { + if (EnsureAscii && error_handler == error_handler_t::keep) + { + // this character was buffered as raw bytes + // below in case it turned out to be part of + // an ill-formed sequence (which is kept as + // is); now that it decoded to a well-formed + // code point, undo that and \u-escape it + // like any other character instead + bytes = bytes_after_last_accept; + } if (codepoint <= 0xFFFF) { write_u_escape(bytes, static_cast(codepoint)); @@ -937,6 +940,44 @@ class serializer break; } + case error_handler_t::keep: + { + // the bytes of this (now abandoned) ill-formed + // sequence seen so far are already buffered below + // and are kept unchanged in the output + if (undumped_chars > 0) + { + // the byte that ended the sequence may be OK + // for itself (e.g., a quote that must still be + // escaped, or the lead byte of a well-formed + // code point), so read it again + --i; + } + else + { + // a byte that cannot start a sequence (e.g., + // 0xFF or a stray continuation byte) is kept + // as well + string_buffer[bytes++] = s[i]; + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + put_buffer(string_buffer, bytes); + bytes = 0; + } + + bytes_after_last_accept = bytes; + undumped_chars = 0; + + // continue processing the string + state = UTF8_ACCEPT; + break; + } + default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } @@ -945,9 +986,12 @@ class serializer default: // decode found yet incomplete multibyte code point { - if (!EnsureAscii) + if (!EnsureAscii || error_handler == error_handler_t::keep) { - // code point will not be escaped - copy byte to buffer + // code point will not be escaped (or will be kept as + // is if it turns out to be ill-formed) - copy byte to + // buffer; dropped again above if it decodes to a + // well-formed code point that needs \u-escaping string_buffer[bytes++] = s[i]; } ++undumped_chars; @@ -998,6 +1042,14 @@ class serializer break; } + case error_handler_t::keep: + { + // write the ill-formed trailing bytes as is; they were + // buffered above regardless of EnsureAscii + put_buffer(string_buffer, bytes); + break; + } + default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } diff --git a/include/nlohmann/detail/string_utils.hpp b/include/nlohmann/detail/string_utils.hpp index 2b6864d0d..4ef748f13 100644 --- a/include/nlohmann/detail/string_utils.hpp +++ b/include/nlohmann/detail/string_utils.hpp @@ -16,6 +16,7 @@ #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -179,5 +180,134 @@ inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std: return state; } +/*! +@brief check a string for well-formed UTF-8 (RFC 3629, section 4) + +Used by the binary readers (CBOR, MessagePack, UBJSON, BJData, BSON) when an +@ref error_handler_t other than `keep` is requested for a text string value +or object key: none of those formats requires a decoder to reject ill-formed +UTF-8 on its own, so the check is opt-in there, unlike the JSON lexer and the +serializer's @ref decode -based escaping, which always run it. + +@param[in] s the string to check +@param[in] first the index to start checking at +@return whether `s.substr(first)` is well-formed UTF-8 + +@sa @ref decode +*/ +template +inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept +{ + std::uint8_t state = UTF8_ACCEPT; + std::uint32_t codepoint = 0; + + for (std::size_t i = first; i < s.size(); ++i) + { + decode(state, codepoint, static_cast(s[i])); + if (state == UTF8_REJECT) + { + return false; + } + } + + return state == UTF8_ACCEPT; +} + +/*! +@brief sanitize a string with ill-formed UTF-8 for @ref error_handler_t::replace or @ref error_handler_t::ignore + +Replaces every maximal ill-formed subsequence with U+FFFD (`replace`) or +drops it (`ignore`), using exactly the same boundaries @ref +serializer::dump_escaped_impl uses while escaping a string: a byte that does +not extend the sequence started by the previous byte(s) is reread as the +start of a new one, instead of being swallowed along with them. + +@pre @a error_handler is @ref error_handler_t::replace or @ref error_handler_t::ignore +@note Well-formed input is copied through unchanged, including bytes (e.g. + control characters or quotes) that @ref serializer::dump_escaped_impl + would itself escape; this function only concerns itself with + well-formedness, not with producing valid JSON text. + +@param[in] s the string to sanitize +@param[in] error_handler @ref error_handler_t::replace or @ref error_handler_t::ignore + +@return @a s with every ill-formed subsequence replaced or removed + +@sa @ref decode +*/ +template +inline StringType sanitize_utf8(const StringType& s, const error_handler_t error_handler) +{ + JSON_ASSERT(error_handler == error_handler_t::replace || error_handler == error_handler_t::ignore); + + StringType result; + result.reserve(s.size()); + + std::uint32_t codepoint = 0; + std::uint8_t state = UTF8_ACCEPT; + // length of result after the last accepted code point + std::size_t result_len_after_last_accept = 0; + // whether bytes of an as yet unresolved sequence were already appended + bool pending = false; + + for (std::size_t i = 0; i < s.size(); ++i) + { + switch (decode(state, codepoint, static_cast(s[i]))) + { + case UTF8_ACCEPT: // decode found a well-formed code point + { + result.push_back(s[i]); + result_len_after_last_accept = result.size(); + pending = false; + break; + } + + case UTF8_REJECT: // decode found an ill-formed byte + { + // in case we saw this byte for the first time, read it again, + // because it may be fine for itself, just not for the + // sequence that came before it + if (pending) + { + --i; + } + + // drop the bytes of the ill-formed sequence buffered below + result.resize(result_len_after_last_accept); + + if (error_handler == error_handler_t::replace) + { + result.append("\xEF\xBF\xBD"); + result_len_after_last_accept = result.size(); + } + + pending = false; + state = UTF8_ACCEPT; + break; + } + + default: // decode found yet incomplete multibyte code point + { + result.push_back(s[i]); + pending = true; + break; + } + } + } + + // the string ended with an incomplete sequence + if (state != UTF8_ACCEPT) + { + result.resize(result_len_after_last_accept); + + if (error_handler == error_handler_t::replace) + { + result.append("\xEF\xBF\xBD"); + } + } + + return result; +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 07b625a89..421feda50 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -195,9 +195,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // used by the vector-returning to_* overloads template using vector_binary_writer = ::nlohmann::detail::binary_writer>; - template static vector_binary_writer vector_writer(std::vector& v) + template static vector_binary_writer vector_writer( + std::vector& v, const ::nlohmann::detail::error_handler_t error_handler = ::nlohmann::detail::binary_writer_default_error_handler()) { - return vector_binary_writer(::nlohmann::detail::output_vector_sink(v)); + return vector_binary_writer(::nlohmann::detail::output_vector_sink(v), error_handler); } JSON_PRIVATE_UNLESS_TESTED: @@ -5583,11 +5584,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template static basic_json from_binary_impl(InputAdapterType ia, const input_format_t format, const bool strict, const bool allow_exceptions, + const error_handler_t error_handler = error_handler_t::keep, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); - binary_reader reader(std::move(ia), format); + binary_reader reader(std::move(ia), format, error_handler); if (!reader.sax_parse(&sdp, strict, tag_handler)) { result = value_t::discarded; @@ -5605,78 +5607,87 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec public: /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ - static std::vector to_cbor(const basic_json& j) + static std::vector to_cbor(const basic_json& j, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_cbor(j); + vector_writer(result, error_handler).write_cbor(j); return result; } /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ - static void to_cbor(const basic_json& j, detail::output_adapter o) + static void to_cbor(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_cbor(j); + binary_writer(o, error_handler).write_cbor(j); } /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ - static void to_cbor(const basic_json& j, detail::output_adapter o) + static void to_cbor(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_cbor(j); + binary_writer(o, error_handler).write_cbor(j); } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ - static std::vector to_msgpack(const basic_json& j) + static std::vector to_msgpack(const basic_json& j, + const error_handler_t error_handler = error_handler_t::keep) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_msgpack(j); + vector_writer(result, error_handler).write_msgpack(j); return result; } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ - static void to_msgpack(const basic_json& j, detail::output_adapter o) + static void to_msgpack(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep) { - binary_writer(o).write_msgpack(j); + binary_writer(o, error_handler).write_msgpack(j); } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ - static void to_msgpack(const basic_json& j, detail::output_adapter o) + static void to_msgpack(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep) { - binary_writer(o).write_msgpack(j); + binary_writer(o, error_handler).write_msgpack(j); } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static std::vector to_ubjson(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_ubjson(j, use_size, use_type); + vector_writer(result, error_handler).write_ubjson(j, use_size, use_type); return result; } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type); } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type); } /// @brief create a BJData serialization of a given JSON value @@ -5684,11 +5695,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_bjdata(const basic_json& j, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2) + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_ubjson(j, use_size, use_type, true, true, version); + vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version); return result; } @@ -5696,42 +5708,47 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2) + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2) + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ - static std::vector to_bson(const basic_json& j) + static std::vector to_bson(const basic_json& j, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_bson(j); + vector_writer(result, error_handler).write_bson(j); return result; } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ - static void to_bson(const basic_json& j, detail::output_adapter o) + static void to_bson(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_bson(j); + binary_writer(o, error_handler).write_bson(j); } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ - static void to_bson(const basic_json& j, detail::output_adapter o) + static void to_bson(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_bson(j); + binary_writer(o, error_handler).write_bson(j); } /// @brief create a BON8 serialization of a given JSON value @@ -5765,9 +5782,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_cbor(InputType&& i, const bool strict = true, const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::cbor, strict, allow_exceptions, tag_handler); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::cbor, strict, allow_exceptions, error_handler, tag_handler); } /// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5778,9 +5796,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_cbor(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::cbor, strict, allow_exceptions, tag_handler); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::cbor, strict, allow_exceptions, error_handler, tag_handler); } template @@ -5801,7 +5820,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, tag_handler); + return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, error_handler_t::keep, tag_handler); } /// @brief create a JSON value from an input in MessagePack format @@ -5810,9 +5829,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::msgpack, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::msgpack, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5822,9 +5842,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::msgpack, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::msgpack, strict, allow_exceptions, error_handler); } template @@ -5852,9 +5873,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::ubjson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::ubjson, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5864,9 +5886,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::ubjson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::ubjson, strict, allow_exceptions, error_handler); } template @@ -5894,9 +5917,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bjdata(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bjdata, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bjdata, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5906,9 +5930,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bjdata(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in BON8 format @@ -5940,9 +5965,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bson, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -5952,9 +5978,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bson, strict, allow_exceptions, error_handler); } template diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 16e87cc86..511401699 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -104,6 +104,10 @@ #define JSON_STRICT_NUL_HANDLING 0 #endif +#ifndef JSON_STRICT_BINARY_UTF8 + #define JSON_STRICT_BINARY_UTF8 0 +#endif + #if JSON_DIAGNOSTICS #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #else @@ -140,14 +144,20 @@ #define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING #endif +#if JSON_STRICT_BINARY_UTF8 + #define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 _sbu8 +#else + #define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8 +#endif + #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ @@ -156,7 +166,8 @@ NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \ NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \ NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \ - NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING) + NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \ + NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ @@ -6151,6 +6162,59 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// how to treat decoding errors +/// +/// @ref basic_json::dump uses this to decide what to do with ill-formed +/// UTF-8 while escaping a string, and the binary writers (@ref +/// basic_json::to_cbor, @ref basic_json::to_ubjson, @ref +/// basic_json::to_bjdata, @ref basic_json::to_bson) use it the same way for +/// string values and object keys. The binary readers (@ref +/// basic_json::from_cbor, @ref basic_json::from_msgpack, @ref +/// basic_json::from_ubjson, @ref basic_json::from_bjdata, @ref +/// basic_json::from_bson) use it to decide whether to check text strings +/// and object keys for well-formed UTF-8 at all, since none of those +/// formats requires a decoder to do so. +enum class error_handler_t +{ + strict, ///< throw a type_error/parse_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore, ///< ignore invalid UTF-8 sequences + keep ///< keep invalid UTF-8 sequences unchanged +}; + +/// the default error handler of the CBOR, UBJSON, BJData, and BSON writers: +/// error_handler_t::strict if JSON_STRICT_BINARY_UTF8 is enabled, otherwise +/// error_handler_t::keep (the behavior before version 3.13.0) +constexpr error_handler_t binary_writer_default_error_handler() noexcept +{ +#if JSON_STRICT_BINARY_UTF8 + return error_handler_t::strict; +#else + return error_handler_t::keep; +#endif +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -6252,13 +6316,14 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally written by Bjƶrn Hoehrmann. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. -The library checks UTF-8 well-formedness (RFC 3629, section 4) in four +The library checks UTF-8 well-formedness (RFC 3629, section 4) in three places, which differ in speed, diagnostics, and how they read the input: -- decode() and @ref is_valid_utf8 below: the serializer (to escape and, in - strict mode, reject ill-formed UTF-8 when dumping a string) and the CBOR, - MessagePack, BSON, UBJSON and BJData readers (to reject ill-formed UTF-8 in - text strings at decode time). +- decode() below: the serializer, to escape and, in strict mode, reject + ill-formed UTF-8 when dumping a string. The CBOR, MessagePack, BSON, + UBJSON and BJData readers do not use it: none of those specs requires a + decoder to reject ill-formed UTF-8 in text strings, so the readers keep + the bytes as is and leave the check to dump() and the binary writers. - the per-lead-byte switch in lexer::scan_string(): JSON text, with a diagnostic for each kind of error. - validate_one_utf8() and valid_utf8_prefix() in string_scan.hpp: the lexer's @@ -6314,19 +6379,19 @@ inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std: } /*! -@brief check whether a string consists solely of valid UTF-8 +@brief check a string for well-formed UTF-8 (RFC 3629, section 4) -Used by the CBOR/MessagePack/BSON/UBJSON binary readers to reject text -strings that are not valid UTF-8 at decode time (RFC 8949 §3.1 and the -MessagePack/BSON specifications all require text strings to be UTF-8), so -that malformed input is caught immediately instead of only surfacing later -as a type_error.316 when the resulting value is dumped. +Used by the binary readers (CBOR, MessagePack, UBJSON, BJData, BSON) when an +@ref error_handler_t other than `keep` is requested for a text string value +or object key: none of those formats requires a decoder to reject ill-formed +UTF-8 on its own, so the check is opt-in there, unlike the JSON lexer and the +serializer's @ref decode -based escaping, which always run it. @param[in] s the string to check -@param[in] first index of the first byte to check; the bytes before it are - assumed to have been validated already and to end on a - code point boundary -@return whether @a s (from index @a first on) is valid UTF-8 +@param[in] first the index to start checking at +@return whether `s.substr(first)` is well-formed UTF-8 + +@sa @ref decode */ template inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept @@ -6346,6 +6411,102 @@ inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noex return state == UTF8_ACCEPT; } +/*! +@brief sanitize a string with ill-formed UTF-8 for @ref error_handler_t::replace or @ref error_handler_t::ignore + +Replaces every maximal ill-formed subsequence with U+FFFD (`replace`) or +drops it (`ignore`), using exactly the same boundaries @ref +serializer::dump_escaped_impl uses while escaping a string: a byte that does +not extend the sequence started by the previous byte(s) is reread as the +start of a new one, instead of being swallowed along with them. + +@pre @a error_handler is @ref error_handler_t::replace or @ref error_handler_t::ignore +@note Well-formed input is copied through unchanged, including bytes (e.g. + control characters or quotes) that @ref serializer::dump_escaped_impl + would itself escape; this function only concerns itself with + well-formedness, not with producing valid JSON text. + +@param[in] s the string to sanitize +@param[in] error_handler @ref error_handler_t::replace or @ref error_handler_t::ignore + +@return @a s with every ill-formed subsequence replaced or removed + +@sa @ref decode +*/ +template +inline StringType sanitize_utf8(const StringType& s, const error_handler_t error_handler) +{ + JSON_ASSERT(error_handler == error_handler_t::replace || error_handler == error_handler_t::ignore); + + StringType result; + result.reserve(s.size()); + + std::uint32_t codepoint = 0; + std::uint8_t state = UTF8_ACCEPT; + // length of result after the last accepted code point + std::size_t result_len_after_last_accept = 0; + // whether bytes of an as yet unresolved sequence were already appended + bool pending = false; + + for (std::size_t i = 0; i < s.size(); ++i) + { + switch (decode(state, codepoint, static_cast(s[i]))) + { + case UTF8_ACCEPT: // decode found a well-formed code point + { + result.push_back(s[i]); + result_len_after_last_accept = result.size(); + pending = false; + break; + } + + case UTF8_REJECT: // decode found an ill-formed byte + { + // in case we saw this byte for the first time, read it again, + // because it may be fine for itself, just not for the + // sequence that came before it + if (pending) + { + --i; + } + + // drop the bytes of the ill-formed sequence buffered below + result.resize(result_len_after_last_accept); + + if (error_handler == error_handler_t::replace) + { + result.append("\xEF\xBF\xBD"); + result_len_after_last_accept = result.size(); + } + + pending = false; + state = UTF8_ACCEPT; + break; + } + + default: // decode found yet incomplete multibyte code point + { + result.push_back(s[i]); + pending = true; + break; + } + } + } + + // the string ended with an incomplete sequence + if (state != UTF8_ACCEPT) + { + result.resize(result_len_after_last_accept); + + if (error_handler == error_handler_t::replace) + { + result.append("\xEF\xBF\xBD"); + } + } + + return result; +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -13452,6 +13613,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -13532,8 +13695,16 @@ class binary_reader @brief create a binary reader @param[in] adapter input adapter to read from + @param[in] format the binary format to parse + @param[in] error_handler how to treat text strings and object keys that + are not well-formed UTF-8; none of the supported formats + requires a decoder to reject those, so the default is to + @ref error_handler_t::keep them unchanged, as every binary + reader did before this parameter existed */ - explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format) + explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json, + const error_handler_t error_handler = error_handler_t::keep) noexcept + : ia(std::move(adapter)), input_format(format), error_handler(error_handler) { (void)detail::is_sax_static_asserts {}; } @@ -13852,7 +14023,7 @@ class binary_reader { if (get_bson_cstr_bulk(result, std::integral_constant {})) { - return true; + return check_string_utf8(result, "key"); } auto out = std::back_inserter(result); @@ -13865,7 +14036,7 @@ class binary_reader } if (current == 0x00) { - return true; + return check_string_utf8(result, "key"); } *out++ = static_cast(current); } @@ -13946,7 +14117,7 @@ class binary_reader "string"), nullptr)); } - return true; + return check_string_utf8(result, "string"); } /*! @@ -14573,7 +14744,7 @@ class binary_reader @return whether string creation completed */ - bool get_cbor_string(string_t& result) + bool get_cbor_string(string_t& result, const char* context = "string") { // number of indefinite-length strings that have been opened and not // closed yet. RFC 8949, Section 3.2.3 does not permit nesting them, @@ -14603,7 +14774,7 @@ class binary_reader { if (--open == 0) { - return true; + return check_string_utf8(result, context); } get(); continue; @@ -14616,7 +14787,7 @@ class binary_reader if (open == 0) { - return true; + return check_string_utf8(result, context); } get(); @@ -14640,7 +14811,7 @@ class binary_reader // EOF and major type 3 (text string) are left to get_cbor_string if (current == char_traits::eof() || (static_cast(current) & 0xE0u) == 0x60u) { - return get_cbor_string(result); + return get_cbor_string(result, "key"); } const char* found = nullptr; @@ -15428,7 +15599,7 @@ class binary_reader @return whether string creation completed */ - bool get_msgpack_string(string_t& result) + bool get_msgpack_string(string_t& result, const char* context = "string") { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) { @@ -15471,25 +15642,25 @@ class binary_reader case 0xBE: case 0xBF: { - return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); + return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result) && check_string_utf8(result, context); } case 0xD9: // str 8 { std::uint8_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context); } case 0xDA: // str 16 { std::uint16_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context); } case 0xDB: // str 32 { std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result) && check_string_utf8(result, context); } default: @@ -15567,7 +15738,7 @@ class binary_reader // byte 0xC1 are left to get_msgpack_string if (current == char_traits::eof()) { - return get_msgpack_string(result); + return get_msgpack_string(result, "key"); } if (current <= 0x7F || current >= 0xE0) { @@ -15583,7 +15754,7 @@ class binary_reader } else { - return get_msgpack_string(result); + return get_msgpack_string(result, "key"); } break; } @@ -15829,7 +16000,7 @@ class binary_reader if (top.is_object) { key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, true, "key") || !sax->key(key))) { return false; } @@ -15851,7 +16022,7 @@ class binary_reader if (top.is_object) { key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false, "key") || !sax->key(key))) { return false; } @@ -15919,7 +16090,7 @@ class binary_reader @return whether string creation completed */ - bool get_ubjson_string(string_t& result, const bool get_char = true) + bool get_ubjson_string(string_t& result, const bool get_char = true, const char* context = "string") { if (get_char) { @@ -15940,31 +16111,31 @@ class binary_reader case 'U': { std::uint8_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'i': { std::int8_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'I': { std::int16_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'l': { std::int32_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'L': { std::int64_t len{}; - return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'u': @@ -15974,7 +16145,7 @@ class binary_reader break; } std::uint16_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'm': @@ -15984,7 +16155,7 @@ class binary_reader break; } std::uint32_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } case 'M': @@ -15994,7 +16165,7 @@ class binary_reader break; } std::uint64_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && get_string(input_format, len, result) && check_string_utf8(result, context); } default: @@ -17468,27 +17639,50 @@ class binary_reader const NumberType len, string_t& result) { - // get_bytes() appends to result, and CBOR indefinite-length strings - // collect all their chunks in the same result; validating only the - // newly read bytes keeps the check linear in the input size - const std::size_t old_size = result.size(); - if (JSON_HEDLEY_UNLIKELY(!get_bytes(format, len, "string", result))) + // Strings are taken as is by default: none of CBOR (RFC 8949 §3.1 + // leaves the choice to the decoder), MessagePack (whose spec + // explicitly allows a str object to contain an invalid byte + // sequence), UBJSON, BJData, or BSON requires a decoder to reject + // ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict, + // rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via + // @ref error_handler, applied once the whole string (all chunks of + // an indefinite-length CBOR string included) has been assembled, by + // @ref check_string_utf8 at the call site. + return get_bytes(format, len, "string", result); + } + + /*! + @brief validate a decoded text string (value or object key) against @ref error_handler + + None of the binary formats requires a decoder to reject ill-formed UTF-8 + in a text string (see @ref get_string), so by default + (@ref error_handler_t::keep) this does nothing. A stricter + @ref error_handler opts into the same well-formedness check @ref + serializer::dump_escaped_impl applies when dumping a string: + @ref error_handler_t::strict rejects ill-formed input with + parse_error.113 (honoring `allow_exceptions` via @a sax), while + @ref error_handler_t::replace / @ref error_handler_t::ignore sanitize + @a result in place, using the exact same rules. + + @param[in,out] result the already assembled string to check + @param[in] context further context information (for diagnostics) + @return whether @a result is acceptable (always true for `keep`) + */ + bool check_string_utf8(string_t& result, const char* context) + { + if (error_handler == error_handler_t::keep || is_valid_utf8(result)) { - return false; + return true; } - // RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications - // all require text strings to be valid UTF-8; reject anything else - // right here so malformed input is caught at decode time instead of - // only surfacing later as a type_error.316 when the value is dumped - // (which would defeat allow_exceptions=false / strict discarding). - if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size))) + if (error_handler == error_handler_t::strict) { - return sax->parse_error(chars_read, get_token_string(), - parse_error::create(113, chars_read, - exception_message(format, "invalid string: ill-formed UTF-8 byte", "string"), nullptr)); + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format, "invalid string: ill-formed UTF-8 byte", context), nullptr)); } + result = sanitize_utf8(result, error_handler); return true; } @@ -17665,6 +17859,9 @@ class binary_reader /// input format const input_format_t input_format = input_format_t::json; + /// how to treat text strings/object keys that are not well-formed UTF-8 + const error_handler_t error_handler = error_handler_t::keep; + /// the SAX parser json_sax_t* sax = nullptr; @@ -20753,6 +20950,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -21120,8 +21319,12 @@ class binary_writer @param[in] sink output sink to write to (a value-type sink such as output_vector_sink, or output_adapter_sink wrapping a type-erased output adapter) + @param[in] error_handler_ how to treat a string value or object key that + is not valid UTF-8 (CBOR, MessagePack, UBJSON, BJData, and BSON; + never consulted by @ref write_bon8) */ - explicit binary_writer(OutputSinkType sink) : oa(std::move(sink)) + explicit binary_writer(OutputSinkType sink, const error_handler_t error_handler_ = binary_writer_default_error_handler()) + : oa(std::move(sink)), error_handler(error_handler_) {} /*! @@ -21134,14 +21337,20 @@ class binary_writer from one. @param[in] adapter output adapter to write to + @param[in] error_handler_ how to treat a string value or object key that + is not valid UTF-8 (CBOR, MessagePack, UBJSON, BJData, and BSON; + never consulted by @ref write_bon8) */ template < typename SinkType = OutputSinkType, typename std::enable_if < std::is_constructible>::value, int >::type = 0 > - explicit binary_writer(output_adapter_t adapter) : oa(SinkType(std::move(adapter))) + explicit binary_writer(output_adapter_t adapter, const error_handler_t error_handler_ = binary_writer_default_error_handler()) + : oa(SinkType(std::move(adapter))), error_handler(error_handler_) {} /*! @param[in] j JSON value to serialize + @throw type_error.316 if a string value or an object key is not valid + UTF-8 @throw type_error.317 if @a j is not an object */ void write_bson(const BasicJsonType& j) @@ -21172,6 +21381,8 @@ class binary_writer /*! @param[in] j JSON value to serialize + @throw type_error.316 if a string value or an object key is not valid + UTF-8 */ void write_cbor(const BasicJsonType& j) { @@ -21238,13 +21449,16 @@ class binary_writer case value_t::string: { + string_t storage; + const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage); + // step 1: write control byte and the string length - write_cbor_head(0x60, j.m_data.m_value.string->size()); + write_cbor_head(0x60, value.size()); // step 2: write the string oa.write_characters( - reinterpret_cast(j.m_data.m_value.string->data()), - j.m_data.m_value.string->size()); + reinterpret_cast(value.data()), + value.size()); break; } @@ -21314,6 +21528,17 @@ class binary_writer // step 2: write each element for (const auto& el : *j.m_data.m_value.object) { + // el.first is checked here, against the object as + // diagnostics context, because write_cbor(el.first) + // converts it to a temporary basic_json that would be + // used as the context instead; for error_handler_t::keep + // and ::replace/::ignore the recursive write_cbor(el.first) + // call below handles the key like any other string, so no + // separate check is needed here for those + if (error_handler == error_handler_t::strict) + { + check_utf8(el.first, j); + } write_cbor(el.first); write_cbor(el.second); } @@ -21461,8 +21686,11 @@ class binary_writer case value_t::string: { + string_t storage; + const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage); + // step 1: write control byte and the string length - const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j); + const auto N = to_msgpack_length(value.size(), j); if (N <= 31) { // fixstr @@ -21489,8 +21717,8 @@ class binary_writer // step 2: write the string oa.write_characters( - reinterpret_cast(j.m_data.m_value.string->data()), - j.m_data.m_value.string->size()); + reinterpret_cast(value.data()), + value.size()); break; } @@ -21637,6 +21865,13 @@ class binary_writer // step 2: write each element for (const auto& el : *j.m_data.m_value.object) { + // as in write_cbor, el.first is checked here against the + // object as diagnostics context; the recursive call below + // handles keep/replace/ignore like any other string + if (error_handler == error_handler_t::strict) + { + check_utf8(el.first, j); + } write_msgpack(el.first); write_msgpack(el.second); } @@ -21656,6 +21891,8 @@ class binary_writer @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false @param[in] bjdata_version which BJData version to use, default is draft2 + @throw type_error.316 if a string value or an object key is not valid + UTF-8 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, @@ -21705,14 +21942,17 @@ class binary_writer case value_t::string: { + string_t storage; + const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage); + if (add_prefix) { oa.write_character(to_char_type('S')); } - write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata); + write_number_with_ubjson_prefix(value.size(), true, use_bjdata); oa.write_characters( - reinterpret_cast(j.m_data.m_value.string->data()), - j.m_data.m_value.string->size()); + reinterpret_cast(value.data()), + value.size()); break; } @@ -21867,10 +22107,12 @@ class binary_writer for (const auto& el : *j.m_data.m_value.object) { - write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); + string_t storage; + const string_t& key = sanitize_utf8_for_write(el.first, j, storage); + write_number_with_ubjson_prefix(key.size(), true, use_bjdata); oa.write_characters( - reinterpret_cast(el.first.data()), - el.first.size()); + reinterpret_cast(key.data()), + key.size()); write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } @@ -21911,8 +22153,12 @@ class binary_writer /*! @return The size of a BSON document entry header, including the id marker and the entry name size (and its null-terminator). + @throw out_of_range.409 if @a name contains U+0000, before anything is + written + @throw type_error.316 if @a name is not valid UTF-8, before anything is + written */ - static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) + std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) { const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) @@ -21920,8 +22166,10 @@ class binary_writer JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); } - static_cast(j); - return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; + string_t storage; + const string_t& sanitized = sanitize_utf8_for_write(name, j, storage); + + return /*id*/ 1ul + sanitized.size() + /*zero-terminator*/1u; } /*! @@ -21941,14 +22189,28 @@ class binary_writer /*! @brief Writes the given @a element_type and @a name to the output adapter + + @a name has already been validated (and, for @ref error_handler_t::strict, + found well-formed) by @ref calc_bson_entry_header_size during the earlier + size pass, so only @ref error_handler_t::replace / @ref + error_handler_t::ignore need to sanitize it again here, to actually write + the bytes that size was computed from. */ void write_bson_entry_header(const string_t& name, const std::uint8_t element_type) { oa.write_character(to_char_type(element_type)); - oa.write_characters( - reinterpret_cast(name.data()), - name.size()); + + if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(name)) + { + oa.write_characters(reinterpret_cast(name.data()), name.size()); + } + else + { + const string_t sanitized = sanitize_utf8(name, error_handler); + oa.write_characters(reinterpret_cast(sanitized.data()), sanitized.size()); + } + // the terminating null byte is written explicitly rather than taken // from the buffer, so that string_t::data() need not be null-terminated oa.write_character(to_char_type(0x00)); @@ -21976,24 +22238,50 @@ class binary_writer /*! @return The size of the BSON-encoded string in @a value + @throw type_error.316 if @a value is not valid UTF-8, before anything is + written + + @note The UTF-8 check is skipped if @a value is already too long for the + 32-bit BSON length field (@ref to_bson_length rejects it later, once + the size of the whole document is known); this also keeps the check + from reading past a StringType that reports a size larger than what + it actually holds. */ - static std::size_t calc_bson_string_size(const string_t& value) + std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j) { + if (JSON_HEDLEY_LIKELY(value_in_range_of(value.size()))) + { + string_t storage; + const string_t& sanitized = sanitize_utf8_for_write(value, j, storage); + return sizeof(std::int32_t) + sanitized.size() + 1ul; + } return sizeof(std::int32_t) + value.size() + 1ul; } /*! @brief Writes a BSON element with key @a name and string value @a value + + @a value has already been validated (and, for @ref error_handler_t::strict, + found well-formed) by @ref calc_bson_string_size during the earlier size + pass, so only @ref error_handler_t::replace / @ref error_handler_t::ignore + need to sanitize it again here, to actually write the bytes that size was + computed from. */ void write_bson_string(const string_t& name, const string_t& value) { write_bson_entry_header(name, 0x02); - write_number(to_bson_length(value.size() + 1ul), true); + const bool sanitize = error_handler != error_handler_t::keep + && error_handler != error_handler_t::strict + && !is_valid_utf8(value); + const string_t sanitized = sanitize ? sanitize_utf8(value, error_handler) : string_t{}; + const string_t& written = sanitize ? sanitized : value; + + write_number(to_bson_length(written.size() + 1ul), true); oa.write_characters( - reinterpret_cast(value.data()), - value.size()); + reinterpret_cast(written.data()), + written.size()); // the terminating null byte is written explicitly rather than taken // from the buffer, so that string_t::data() need not be null-terminated oa.write_character(to_char_type(0x00)); @@ -22107,8 +22395,10 @@ class binary_writer is neither an object nor an array @throw out_of_range.415 if @a j is binary with a subtype that does not fit into a byte, before anything is written + @throw type_error.316 if @a j is a string that is not valid UTF-8, before + anything is written */ - static std::size_t calc_bson_value_size(const BasicJsonType& j) + std::size_t calc_bson_value_size(const BasicJsonType& j) { switch (j.type()) { @@ -22128,7 +22418,7 @@ class binary_writer return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned); case value_t::string: - return calc_bson_string_size(*j.m_data.m_value.string); + return calc_bson_string_size(*j.m_data.m_value.string, j); case value_t::null: return 0ul; @@ -22241,8 +22531,10 @@ class binary_writer written @throw out_of_range.415 if a binary value's subtype does not fit into a byte, before anything is written + @throw type_error.316 if a string value or a key is not valid UTF-8, + before anything is written */ - static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector& nested_sizes) + std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector& nested_sizes) { // the object or array whose entries are being sized, and the ones it // is in; nothing is allocated unless the document nests @@ -23119,7 +23411,7 @@ class binary_writer */ void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context) { - check_bon8_utf8(s, context); + check_utf8(s, context); // a string that follows another string terminates it if (string_open) @@ -23149,7 +23441,7 @@ class binary_writer @throw type_error.316 if @a s is not valid UTF-8; the message names the first byte of the first invalid or incomplete sequence */ - static void check_bon8_utf8(const string_t& s, const BasicJsonType& context) + static void check_utf8(const string_t& s, const BasicJsonType& context) { static_cast(context); // only used when exceptions are enabled const auto* data = reinterpret_cast(s.data()); @@ -23160,6 +23452,57 @@ class binary_writer } } + /*! + @brief return @a s as it should be written, honoring @ref error_handler + + Used by @ref write_cbor, @ref write_msgpack, @ref write_ubjson (and so + @ref write_bjdata), and the BSON writing functions for string values and + object keys; never by @ref write_bon8, which always validates, since UTF-8 + lead bytes are structural there. + + - @ref error_handler_t::keep: @a s is returned unchanged, without even + checking it (the behavior of release 3.12.0 and earlier). + - @ref error_handler_t::strict: @ref check_utf8 is called, which throws + type_error.316 if @a s is not valid UTF-8. + - @ref error_handler_t::replace / @ref error_handler_t::ignore: @a s is + sanitized into @a storage with exactly the rules @ref + serializer::dump_escaped_impl uses, so that parsing what @ref + basic_json::dump produces for the same string and the same handler + yields the same result. + + Well-formed input is never copied: this returns a reference to @a s + itself in every case but a sanitized `replace`/`ignore` one, so @a + storage must outlive the returned reference only then. + + @param[in] s the string (value or object key) to write + @param[in] context the value @a s belongs to (for diagnostics) + @param[out] storage backing storage for a sanitized copy + + @return a reference to @a s, or to @a storage once it holds a sanitized copy + */ + const string_t& sanitize_utf8_for_write(const string_t& s, const BasicJsonType& context, string_t& storage) const + { + switch (error_handler) + { + case error_handler_t::keep: + return s; + + case error_handler_t::strict: + check_utf8(s, context); + return s; + + case error_handler_t::replace: + case error_handler_t::ignore: + default: + if (is_valid_utf8(s)) + { + return s; + } + storage = sanitize_utf8(s, error_handler); + return storage; + } + } + /*! @brief write an integer in the shortest encoding @@ -23488,6 +23831,10 @@ class binary_writer /// the output OutputSinkType oa; + + /// how to treat a string value or object key that is not valid UTF-8 + /// (CBOR, MessagePack, UBJSON, BJData, and BSON; not BON8) + const error_handler_t error_handler = binary_writer_default_error_handler(); }; } // namespace detail @@ -24649,6 +24996,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -24668,14 +25017,6 @@ namespace detail // serialization // /////////////////// -/// how to treat decoding errors -enum class error_handler_t -{ - strict, ///< throw a type_error exception in case of invalid UTF-8 - replace, ///< replace invalid UTF-8 sequences with U+FFFD - ignore ///< ignore invalid UTF-8 sequences -}; - template class serializer { @@ -25466,6 +25807,16 @@ class serializer // EnsureAscii parameter is used, non-ASCII characters if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F))) { + if (EnsureAscii && error_handler == error_handler_t::keep) + { + // this character was buffered as raw bytes + // below in case it turned out to be part of + // an ill-formed sequence (which is kept as + // is); now that it decoded to a well-formed + // code point, undo that and \u-escape it + // like any other character instead + bytes = bytes_after_last_accept; + } if (codepoint <= 0xFFFF) { write_u_escape(bytes, static_cast(codepoint)); @@ -25564,6 +25915,44 @@ class serializer break; } + case error_handler_t::keep: + { + // the bytes of this (now abandoned) ill-formed + // sequence seen so far are already buffered below + // and are kept unchanged in the output + if (undumped_chars > 0) + { + // the byte that ended the sequence may be OK + // for itself (e.g., a quote that must still be + // escaped, or the lead byte of a well-formed + // code point), so read it again + --i; + } + else + { + // a byte that cannot start a sequence (e.g., + // 0xFF or a stray continuation byte) is kept + // as well + string_buffer[bytes++] = s[i]; + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + put_buffer(string_buffer, bytes); + bytes = 0; + } + + bytes_after_last_accept = bytes; + undumped_chars = 0; + + // continue processing the string + state = UTF8_ACCEPT; + break; + } + default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } @@ -25572,9 +25961,12 @@ class serializer default: // decode found yet incomplete multibyte code point { - if (!EnsureAscii) + if (!EnsureAscii || error_handler == error_handler_t::keep) { - // code point will not be escaped - copy byte to buffer + // code point will not be escaped (or will be kept as + // is if it turns out to be ill-formed) - copy byte to + // buffer; dropped again above if it decodes to a + // well-formed code point that needs \u-escaping string_buffer[bytes++] = s[i]; } ++undumped_chars; @@ -25625,6 +26017,14 @@ class serializer break; } + case error_handler_t::keep: + { + // write the ill-formed trailing bytes as is; they were + // buffered above regardless of EnsureAscii + put_buffer(string_buffer, bytes); + break; + } + default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } @@ -26732,9 +27132,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // used by the vector-returning to_* overloads template using vector_binary_writer = ::nlohmann::detail::binary_writer>; - template static vector_binary_writer vector_writer(std::vector& v) + template static vector_binary_writer vector_writer( + std::vector& v, const ::nlohmann::detail::error_handler_t error_handler = ::nlohmann::detail::binary_writer_default_error_handler()) { - return vector_binary_writer(::nlohmann::detail::output_vector_sink(v)); + return vector_binary_writer(::nlohmann::detail::output_vector_sink(v), error_handler); } JSON_PRIVATE_UNLESS_TESTED: @@ -32120,11 +32521,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template static basic_json from_binary_impl(InputAdapterType ia, const input_format_t format, const bool strict, const bool allow_exceptions, + const error_handler_t error_handler = error_handler_t::keep, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; detail::json_sax_dom_parser sdp(result, allow_exceptions); - binary_reader reader(std::move(ia), format); + binary_reader reader(std::move(ia), format, error_handler); if (!reader.sax_parse(&sdp, strict, tag_handler)) { result = value_t::discarded; @@ -32142,78 +32544,87 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec public: /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ - static std::vector to_cbor(const basic_json& j) + static std::vector to_cbor(const basic_json& j, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_cbor(j); + vector_writer(result, error_handler).write_cbor(j); return result; } /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ - static void to_cbor(const basic_json& j, detail::output_adapter o) + static void to_cbor(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_cbor(j); + binary_writer(o, error_handler).write_cbor(j); } /// @brief create a CBOR serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ - static void to_cbor(const basic_json& j, detail::output_adapter o) + static void to_cbor(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_cbor(j); + binary_writer(o, error_handler).write_cbor(j); } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ - static std::vector to_msgpack(const basic_json& j) + static std::vector to_msgpack(const basic_json& j, + const error_handler_t error_handler = error_handler_t::keep) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_msgpack(j); + vector_writer(result, error_handler).write_msgpack(j); return result; } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ - static void to_msgpack(const basic_json& j, detail::output_adapter o) + static void to_msgpack(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep) { - binary_writer(o).write_msgpack(j); + binary_writer(o, error_handler).write_msgpack(j); } /// @brief create a MessagePack serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ - static void to_msgpack(const basic_json& j, detail::output_adapter o) + static void to_msgpack(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = error_handler_t::keep) { - binary_writer(o).write_msgpack(j); + binary_writer(o, error_handler).write_msgpack(j); } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static std::vector to_ubjson(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_ubjson(j, use_size, use_type); + vector_writer(result, error_handler).write_ubjson(j, use_size, use_type); return result; } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type); } /// @brief create a UBJSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type); } /// @brief create a BJData serialization of a given JSON value @@ -32221,11 +32632,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_bjdata(const basic_json& j, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2) + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_ubjson(j, use_size, use_type, true, true, version); + vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version); return result; } @@ -32233,42 +32645,47 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2) + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false, - const bjdata_version_t version = bjdata_version_t::draft2) + const bjdata_version_t version = bjdata_version_t::draft2, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); + binary_writer(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ - static std::vector to_bson(const basic_json& j) + static std::vector to_bson(const basic_json& j, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { std::vector result; result.reserve(detail::binary_reserve_hint(j)); - vector_writer(result).write_bson(j); + vector_writer(result, error_handler).write_bson(j); return result; } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ - static void to_bson(const basic_json& j, detail::output_adapter o) + static void to_bson(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_bson(j); + binary_writer(o, error_handler).write_bson(j); } /// @brief create a BSON serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ - static void to_bson(const basic_json& j, detail::output_adapter o) + static void to_bson(const basic_json& j, detail::output_adapter o, + const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - binary_writer(o).write_bson(j); + binary_writer(o, error_handler).write_bson(j); } /// @brief create a BON8 serialization of a given JSON value @@ -32302,9 +32719,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_cbor(InputType&& i, const bool strict = true, const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::cbor, strict, allow_exceptions, tag_handler); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::cbor, strict, allow_exceptions, error_handler, tag_handler); } /// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32315,9 +32733,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_cbor(IteratorType first, SentinelType last, const bool strict = true, const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::cbor, strict, allow_exceptions, tag_handler); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::cbor, strict, allow_exceptions, error_handler, tag_handler); } template @@ -32338,7 +32757,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, tag_handler); + return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, error_handler_t::keep, tag_handler); } /// @brief create a JSON value from an input in MessagePack format @@ -32347,9 +32766,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::msgpack, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::msgpack, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32359,9 +32779,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_msgpack(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::msgpack, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::msgpack, strict, allow_exceptions, error_handler); } template @@ -32389,9 +32810,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::ubjson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::ubjson, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32401,9 +32823,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_ubjson(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::ubjson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::ubjson, strict, allow_exceptions, error_handler); } template @@ -32431,9 +32854,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bjdata(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bjdata, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bjdata, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32443,9 +32867,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bjdata(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in BON8 format @@ -32477,9 +32902,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(InputType&& i, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::forward(i)), input_format_t::bson, strict, allow_exceptions, error_handler); } /// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -32489,9 +32915,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json from_bson(IteratorType first, SentinelType last, const bool strict = true, - const bool allow_exceptions = true) + const bool allow_exceptions = true, + const error_handler_t error_handler = error_handler_t::keep) { - return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bson, strict, allow_exceptions); + return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bson, strict, allow_exceptions, error_handler); } template @@ -33734,6 +34161,7 @@ struct formatter // NOLINT(cert-dcl58-c #undef JSON_BRACE_INIT_COPY_SEMANTICS #undef JSON_PRECISE_STREAM_POSITION #undef JSON_STRICT_NUL_HANDLING + #undef JSON_STRICT_BINARY_UTF8 #endif // #include diff --git a/tests/src/unit-binary_utf8_error_handler.cpp b/tests/src/unit-binary_utf8_error_handler.cpp new file mode 100644 index 000000000..85505bffb --- /dev/null +++ b/tests/src/unit-binary_utf8_error_handler.cpp @@ -0,0 +1,362 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +#include +#include + +namespace +{ + +struct ill_formed_case +{ + const char* name; + std::string bytes; +}; + +// RFC 3629 ill-formed sequences used throughout this file, plus one +// well-formed sequence for contrast +const std::vector ill_formed_cases = +{ + {"overlong", "\xC0\xAE"}, + {"lone_0xFF", "\xFF"}, + {"truncated", "\xE2\x82"}, + {"surrogate", "\xED\xA0\x80"}, +}; + +const std::string valid_sequence = "\xC3\xA9"; // U+00E9, "Ć©" + +using eh = json::error_handler_t; +const std::vector all_handlers = {eh::strict, eh::replace, eh::ignore, eh::keep}; + +// what dump()+parse() produces for a sanitizing error_handler; this is the +// ground truth every binary writer/reader is checked against +std::string dump_and_parse(const std::string& raw, eh error_handler) +{ + return json::parse(json(raw).dump(-1, ' ', false, error_handler)).get(); +} + +} // namespace + +TEST_CASE("UTF-8 error_handler for the binary readers and writers") +{ + SECTION("writers: string value") + { + for (const auto& c : ill_formed_cases) + { + CAPTURE(c.name); + const json jval = c.bytes; + + CHECK_THROWS_AS(json::to_cbor(jval, eh::strict), json::type_error&); + CHECK_THROWS_AS(json::to_msgpack(jval, eh::strict), json::type_error&); + CHECK_THROWS_AS(json::to_ubjson(jval, false, false, eh::strict), json::type_error&); + CHECK_THROWS_AS(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::strict), json::type_error&); + { + json jobj; + jobj["k"] = jval; + CHECK_THROWS_AS(json::to_bson(jobj, eh::strict), json::type_error&); + } + + for (const auto h : + { + eh::replace, eh::ignore + }) + { + CAPTURE(static_cast(h)); + const std::string expected = dump_and_parse(c.bytes, h); + + CHECK(json::from_cbor(json::to_cbor(jval, h)).get() == expected); + CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get() == expected); + CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get() == expected); + CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get() == expected); + { + json jobj; + jobj["k"] = jval; + const auto bytes = json::to_bson(jobj, h); + CHECK(json::from_bson(bytes)["k"].get() == expected); + } + } + + // keep: the writer passes the ill-formed bytes through unchanged, + // exactly as every binary writer did before this parameter existed + CHECK(json::from_cbor(json::to_cbor(jval, eh::keep)).get() == c.bytes); + CHECK(json::from_msgpack(json::to_msgpack(jval, eh::keep)).get() == c.bytes); + CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, eh::keep)).get() == c.bytes); + CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep)).get() == c.bytes); + { + json jobj; + jobj["k"] = jval; + const auto bytes = json::to_bson(jobj, eh::keep); + CHECK(json::from_bson(bytes)["k"].get() == c.bytes); + } + } + } + + SECTION("writers: object key") + { + for (const auto& c : ill_formed_cases) + { + CAPTURE(c.name); + json jobj; + jobj[c.bytes] = 1; + + CHECK_THROWS_AS(json::to_cbor(jobj, eh::strict), json::type_error&); + CHECK_THROWS_AS(json::to_msgpack(jobj, eh::strict), json::type_error&); + CHECK_THROWS_AS(json::to_ubjson(jobj, false, false, eh::strict), json::type_error&); + CHECK_THROWS_AS(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::strict), json::type_error&); + CHECK_THROWS_AS(json::to_bson(jobj, eh::strict), json::type_error&); + + for (const auto h : + { + eh::replace, eh::ignore + }) + { + CAPTURE(static_cast(h)); + const std::string expected = dump_and_parse(c.bytes, h); + + CHECK(json::from_cbor(json::to_cbor(jobj, h)).begin().key() == expected); + CHECK(json::from_msgpack(json::to_msgpack(jobj, h)).begin().key() == expected); + CHECK(json::from_ubjson(json::to_ubjson(jobj, false, false, h)).begin().key() == expected); + CHECK(json::from_bjdata(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, h)).begin().key() == expected); + CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == expected); + } + + // keep: object keys round-trip unchanged too + CHECK(json::from_cbor(json::to_cbor(jobj, eh::keep)).begin().key() == c.bytes); + CHECK(json::from_msgpack(json::to_msgpack(jobj, eh::keep)).begin().key() == c.bytes); + CHECK(json::from_ubjson(json::to_ubjson(jobj, false, false, eh::keep)).begin().key() == c.bytes); + CHECK(json::from_bjdata(json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::keep)).begin().key() == c.bytes); + CHECK(json::from_bson(json::to_bson(jobj, eh::keep)).begin().key() == c.bytes); + } + } + + SECTION("readers: string value") + { + for (const auto& c : ill_formed_cases) + { + CAPTURE(c.name); + + // bytes produced the lenient (keep) way, as any binary reader + // accepted them before this parameter existed + const auto cbor_bytes = json::to_cbor(json(c.bytes), eh::keep); + const auto msgpack_bytes = json::to_msgpack(json(c.bytes)); // to_msgpack has no error_handler; always pass-through + const auto ubjson_bytes = json::to_ubjson(json(c.bytes), false, false, eh::keep); + const auto bjdata_bytes = json::to_bjdata(json(c.bytes), false, false, json::bjdata_version_t::draft2, eh::keep); + const auto bson_bytes = [&c] + { + json jobj; + jobj["k"] = c.bytes; + return json::to_bson(jobj, eh::keep); + }(); + + // keep (the default): bytes are kept unchanged + CHECK(json::from_cbor(cbor_bytes).get() == c.bytes); + CHECK(json::from_msgpack(msgpack_bytes).get() == c.bytes); + CHECK(json::from_ubjson(ubjson_bytes).get() == c.bytes); + CHECK(json::from_bjdata(bjdata_bytes).get() == c.bytes); + CHECK(json::from_bson(bson_bytes)["k"].get() == c.bytes); + + // strict: parse_error.113, discarded (not thrown) when allow_exceptions is false + CHECK_THROWS_AS(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict), json::parse_error&); + CHECK(json::from_cbor(cbor_bytes, true, false, json::cbor_tag_handler_t::error, eh::strict).is_discarded()); + CHECK_THROWS_AS(json::from_msgpack(msgpack_bytes, true, true, eh::strict), json::parse_error&); + CHECK(json::from_msgpack(msgpack_bytes, true, false, eh::strict).is_discarded()); + CHECK_THROWS_AS(json::from_ubjson(ubjson_bytes, true, true, eh::strict), json::parse_error&); + CHECK(json::from_ubjson(ubjson_bytes, true, false, eh::strict).is_discarded()); + CHECK_THROWS_AS(json::from_bjdata(bjdata_bytes, true, true, eh::strict), json::parse_error&); + CHECK(json::from_bjdata(bjdata_bytes, true, false, eh::strict).is_discarded()); + CHECK_THROWS_AS(json::from_bson(bson_bytes, true, true, eh::strict), json::parse_error&); + CHECK(json::from_bson(bson_bytes, true, false, eh::strict).is_discarded()); + + // replace / ignore: match what dump() would have sanitized the same bytes to + for (const auto h : + { + eh::replace, eh::ignore + }) + { + CAPTURE(static_cast(h)); + const std::string expected = dump_and_parse(c.bytes, h); + + CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).get() == expected); + CHECK(json::from_msgpack(msgpack_bytes, true, true, h).get() == expected); + CHECK(json::from_ubjson(ubjson_bytes, true, true, h).get() == expected); + CHECK(json::from_bjdata(bjdata_bytes, true, true, h).get() == expected); + CHECK(json::from_bson(bson_bytes, true, true, h)["k"].get() == expected); + } + } + } + + SECTION("readers: object key") + { + for (const auto& c : ill_formed_cases) + { + CAPTURE(c.name); + + json jobj; + jobj[c.bytes] = 1; + const auto cbor_bytes = json::to_cbor(jobj, eh::keep); + const auto msgpack_bytes = json::to_msgpack(jobj); + const auto ubjson_bytes = json::to_ubjson(jobj, false, false, eh::keep); + const auto bjdata_bytes = json::to_bjdata(jobj, false, false, json::bjdata_version_t::draft2, eh::keep); + const auto bson_bytes = json::to_bson(jobj, eh::keep); + + CHECK(json::from_cbor(cbor_bytes).begin().key() == c.bytes); + CHECK(json::from_msgpack(msgpack_bytes).begin().key() == c.bytes); + CHECK(json::from_ubjson(ubjson_bytes).begin().key() == c.bytes); + CHECK(json::from_bjdata(bjdata_bytes).begin().key() == c.bytes); + CHECK(json::from_bson(bson_bytes).begin().key() == c.bytes); + + CHECK_THROWS_AS(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict), json::parse_error&); + CHECK_THROWS_AS(json::from_msgpack(msgpack_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(json::from_ubjson(ubjson_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(json::from_bjdata(bjdata_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(json::from_bson(bson_bytes, true, true, eh::strict), json::parse_error&); + + for (const auto h : + { + eh::replace, eh::ignore + }) + { + CAPTURE(static_cast(h)); + const std::string expected = dump_and_parse(c.bytes, h); + + CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).begin().key() == expected); + CHECK(json::from_msgpack(msgpack_bytes, true, true, h).begin().key() == expected); + CHECK(json::from_ubjson(ubjson_bytes, true, true, h).begin().key() == expected); + CHECK(json::from_bjdata(bjdata_bytes, true, true, h).begin().key() == expected); + CHECK(json::from_bson(bson_bytes, true, true, h).begin().key() == expected); + } + } + } + + SECTION("well-formed UTF-8 is unaffected by error_handler") + { + const json jval = valid_sequence; + json jobj; + jobj[valid_sequence] = valid_sequence; + + for (const auto h : all_handlers) + { + CAPTURE(static_cast(h)); + + CHECK(json::from_cbor(json::to_cbor(jval, h)).get() == valid_sequence); + CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get() == valid_sequence); + CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get() == valid_sequence); + CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get() == valid_sequence); + CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == valid_sequence); + + CHECK(json::from_cbor(json::to_cbor(jval, eh::keep), true, true, json::cbor_tag_handler_t::error, h).get() == valid_sequence); + CHECK(json::from_msgpack(json::to_msgpack(jval), true, true, h).get() == valid_sequence); + } + } + + SECTION("dump() with error_handler_t::keep writes raw bytes as is") + { + for (const auto& c : ill_formed_cases) + { + CAPTURE(c.name); + + const json jval = c.bytes; + const std::string dumped = jval.dump(-1, ' ', false, eh::keep); + CHECK(dumped.find(c.bytes) != std::string::npos); + + // even with ensure_ascii, the ill-formed bytes are written as is + const std::string dumped_ascii = jval.dump(-1, ' ', true, eh::keep); + CHECK(dumped_ascii.find(c.bytes) != std::string::npos); + } + + // well-formed characters around an ill-formed sequence are still + // escaped as usual under ensure_ascii + const json mixed = valid_sequence + ill_formed_cases[1].bytes; // "Ć©" + lone 0xFF + const std::string dumped_mixed = mixed.dump(-1, ' ', true, eh::keep); + CHECK(dumped_mixed.find("\\u00e9") != std::string::npos); + CHECK(dumped_mixed.find(ill_formed_cases[1].bytes) != std::string::npos); + + // the byte that ends an ill-formed sequence is read again, so a quote, + // a backslash, or a control character after it is still escaped, and + // a well-formed code point after it is escaped under ensure_ascii + for (const bool ensure_ascii : + { + false, true + }) + { + CAPTURE(ensure_ascii); + CHECK(json("\xC3\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\"\""); + CHECK(json("\xC3\\").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\\\""); + CHECK(json("\xC3\n").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\n\""); + CHECK(json("\xE2\x82\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xE2\x82\\\"\""); + CHECK(json("\xFF\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xFF\\\"\""); + CHECK(json("a\xE2\x82").dump(-1, ' ', ensure_ascii, eh::keep) == "\"a\xE2\x82\""); + } + CHECK(json("\xC3\xC3\xA9").dump(-1, ' ', false, eh::keep) == "\"\xC3\xC3\xA9\""); + CHECK(json("\xC3\xC3\xA9").dump(-1, ' ', true, eh::keep) == "\"\xC3\\u00e9\""); + } + + SECTION("to_msgpack defaults to keep; to_bon8 is not affected by error_handler") + { + const json jval = ill_formed_cases[1].bytes; // lone 0xFF + + // to_msgpack's error_handler defaults to keep, as MessagePack's spec + // allows any bytes in a str, so the bytes are passed through + CHECK(json::to_msgpack(jval) == json::to_msgpack(jval, eh::keep)); + CHECK(json::from_msgpack(json::to_msgpack(jval)).get() == ill_formed_cases[1].bytes); + + // the diagnostics context of an ill-formed key is the object + json jobj; + jobj["\xFF"] = 1; + CHECK_THROWS_WITH_AS(json::to_msgpack(jobj, eh::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); + + // to_bon8 has no error_handler parameter; UTF-8 is structural for + // BON8, so it always rejects ill-formed input + CHECK_THROWS_AS(json::to_bon8(jval), json::type_error&); + } + + SECTION("allow_exceptions=false with error_handler_t::strict discards the value") + { + const auto bytes = json::to_cbor(json(ill_formed_cases[0].bytes), eh::keep); + const json result = json::from_cbor(bytes, true, false, json::cbor_tag_handler_t::error, eh::strict); + CHECK(result.is_discarded()); + } + + SECTION("default parameters are unchanged") + { + const json jval = ill_formed_cases[0].bytes; + + // to_*: the default error_handler is keep, so ill-formed bytes are + // written unchanged, exactly as in release 3.12.0 (it is strict only + // if JSON_STRICT_BINARY_UTF8 is enabled, see + // unit-binary_utf8_strict.cpp) + CHECK(json::to_cbor(jval) == json::to_cbor(jval, eh::keep)); + CHECK(json::to_ubjson(jval) == json::to_ubjson(jval, false, false, eh::keep)); + CHECK(json::to_bjdata(jval) == json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep)); + { + json jobj; + jobj["k"] = jval; + CHECK(json::to_bson(jobj) == json::to_bson(jobj, eh::keep)); + } + + // from_*: the default error_handler is keep, so ill-formed bytes are + // still accepted unchanged, exactly as in release 3.12.0 + const auto cbor_bytes = json::to_cbor(jval, eh::keep); + CHECK(json::from_cbor(cbor_bytes).get() == ill_formed_cases[0].bytes); + const auto ubjson_bytes = json::to_ubjson(jval, false, false, eh::keep); + CHECK(json::from_ubjson(ubjson_bytes).get() == ill_formed_cases[0].bytes); + const auto bjdata_bytes = json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep); + CHECK(json::from_bjdata(bjdata_bytes).get() == ill_formed_cases[0].bytes); + const auto msgpack_bytes = json::to_msgpack(jval); + CHECK(json::from_msgpack(msgpack_bytes).get() == ill_formed_cases[0].bytes); + json bson_obj; + bson_obj["k"] = jval; + const auto bson_bytes = json::to_bson(bson_obj, eh::keep); + CHECK(json::from_bson(bson_bytes)["k"].get() == ill_formed_cases[0].bytes); + } +} diff --git a/tests/src/unit-binary_utf8_strict.cpp b/tests/src/unit-binary_utf8_strict.cpp index c83b5938c..2ac8aabf4 100644 --- a/tests/src/unit-binary_utf8_strict.cpp +++ b/tests/src/unit-binary_utf8_strict.cpp @@ -100,11 +100,23 @@ TEST_CASE("JSON_STRICT_BINARY_UTF8 (see #5529, #5651)") CHECK_THROWS_WITH_AS(json::to_bson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); } + SECTION("an explicit error_handler overrides the default") + { + // the macro only changes the default of the error_handler parameter + CHECK(json::to_cbor(json("\xFF"), json::error_handler_t::keep) == std::vector({0x61, 0xff})); + CHECK(json::to_ubjson(json("\xFF"), false, false, json::error_handler_t::keep) == std::vector({'S', 'i', 1, 0xff})); + CHECK(json::to_bjdata(json("\xFF"), false, false, json::bjdata_version_t::draft2, json::error_handler_t::keep) == std::vector({'S', 'i', 1, 0xff})); + CHECK(json::from_bson(json::to_bson(json{{"s", "\xFF"}}, json::error_handler_t::keep)) == json{{"s", "\xFF"}}); + CHECK(json::to_cbor(json("\xFF"), json::error_handler_t::replace) == std::vector({0x63, 0xef, 0xbf, 0xbd})); + } + SECTION("MessagePack and BON8 are unaffected") { - // MessagePack allows any bytes in a str, so to_msgpack() writes them as - // is; BON8 always checks, because the lead bytes mark where strings end + // MessagePack allows any bytes in a str, so to_msgpack() still + // defaults to keep (strict only if passed explicitly); BON8 always + // checks, because the lead bytes mark where strings end CHECK(json::to_msgpack(json("\xFF")) == std::vector({0xa1, 0xff})); + CHECK_THROWS_AS(json::to_msgpack(json("\xFF"), json::error_handler_t::strict), json::type_error&); CHECK_THROWS_AS(json::to_bon8(json("\xFF")), json::type_error&); } } From 0c4462676dfe0787ed6788079821323a40416234 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 12:23:53 +0200 Subject: [PATCH 26/27] Document options to reduce compile times (#5611) * Document options to reduce compile times Add an integration page that collects the ways to reduce compile times with measurements: json_fwd.hpp in headers, JSON_NO_AUTOMATIC_UDLS, explicit instantiation with extern template, modules, and precompiled headers, and notes that JSON_NO_IO and JSON_USE_GLOBAL_UDLS have no measurable effect. Signed-off-by: Niels Lohmann * Include only json_literals.hpp in the JSON_NO_AUTOMATIC_UDLS examples json_literals.hpp includes json.hpp itself, so including both is redundant. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../docs/api/macros/json_no_automatic_udls.md | 3 +- docs/mkdocs/docs/integration/compile_times.md | 149 ++++++++++++++++++ docs/mkdocs/docs/integration/index.md | 2 +- docs/mkdocs/mkdocs.yml | 1 + 4 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 docs/mkdocs/docs/integration/compile_times.md diff --git a/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md b/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md index acb6a356e..1235d634d 100644 --- a/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md +++ b/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md @@ -43,9 +43,9 @@ By default, `#!cpp JSON_NO_AUTOMATIC_UDLS` is not defined, and ` // this file uses the literals, so it includes them explicitly + // (the header includes itself) #include int main() @@ -62,6 +62,7 @@ By default, `#!cpp JSON_NO_AUTOMATIC_UDLS` is not defined, and `` pays for parsing the header and instantiating what it uses. This page lists the options to reduce +that cost, ordered by how much they typically save. + +!!! info "Measurements" + + The numbers below are medians of nine runs compiling a single translation unit with `-std=c++17 -c` against the + single-header version, with Apple clang and GCC 16 on macOS (Apple silicon). They show the order of magnitude to + expect; measure your own code before and after a change. + +## Include `json_fwd.hpp` in headers + +Header files that only need to *name* the `json` type — for function declarations, members held by pointer or +reference, or friend declarations — can include `` instead of ``. It only +forward-declares `basic_json`, `json`, `ordered_json`, `json_pointer`, and `adl_serializer`. The translation units that +actually use the values then include ``. + +```cpp title="person.hpp" +#pragma once +#include + +struct person; +void to_json(nlohmann::json& j, const person& p); +void from_json(const nlohmann::json& j, person& p); +``` + +```cpp title="person.cpp" +#include "person.hpp" +#include + +void to_json(nlohmann::json& j, const person& p) { /* ... */ } +void from_json(const nlohmann::json& j, person& p) { /* ... */ } +``` + +| Compiler | `json.hpp` (`-O0`) | `json_fwd.hpp` (`-O0`) | Change | +|-------------|-------------------:|-----------------------:|-------:| +| Apple clang | 704 ms | 329 ms | āˆ’53% | +| GCC 16 | 779 ms | 242 ms | āˆ’69% | + +This is the most effective option, because it avoids the full header in every translation unit that includes +*your* headers. + +## Opt out of the automatic user-defined string literals + +The user-defined string literals [`operator""_json`](../api/operator_literal_json.md) and +[`operator""_json_pointer`](../api/operator_literal_json_pointer.md) are ordinary inline functions whose bodies call the +parser. As `` includes them by default, every translation unit instantiates the parser, even if it +never parses anything itself. + +Define [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md) for the whole project and include +`` instead of `` in the files that use the literals (it includes +`` itself): + +```cmake +target_compile_definitions(my_target PRIVATE JSON_NO_AUTOMATIC_UDLS) +``` + +```cpp +#include // only where "..."_json is used; includes +``` + +The saving applies to translation units that do not parse JSON, for example ones that define types and their +conversions or only pass `json` values around: + +| Compiler | Translation unit | Default (`-O0` / `-O2`) | `JSON_NO_AUTOMATIC_UDLS` (`-O0` / `-O2`) | Change | +|-------------|------------------|------------------------:|-----------------------------------------:|------------:| +| Apple clang | model | 776 ms / 846 ms | 629 ms / 692 ms | āˆ’19% / āˆ’18% | +| GCC 16 | model | 1022 ms / 1120 ms | 882 ms / 965 ms | āˆ’14% / āˆ’14% | +| Apple clang | parsing | 992 ms / 1815 ms | 1006 ms / 1823 ms | +1% / 0% | +| GCC 16 | parsing | 2018 ms / 3420 ms | 1990 ms / 3454 ms | āˆ’1% / +1% | + +Translation units that include only the header save up to a third. Translation units that parse anyway instantiate +the parser regardless and see no difference. + +## Instantiate `basic_json` once + +Each translation unit instantiates the member functions of `nlohmann::json` it uses. An explicit instantiation +declaration tells the compiler that the non-template members are instantiated elsewhere, so it can skip them: + +```cpp title="json_instance.hpp" +#pragma once +#include + +extern template class nlohmann::basic_json<>; +``` + +```cpp title="json_instance.cpp" +#include "json_instance.hpp" + +template class nlohmann::basic_json<>; +``` + +Include `json_instance.hpp` instead of `` and compile and link `json_instance.cpp` once. + +| Compiler | Translation unit | Default (`-O0` / `-O2`) | `extern template` (`-O0` / `-O2`) | Change | +|-------------|---------------------|------------------------:|----------------------------------:|------------:| +| Apple clang | parsing | 992 ms / 1815 ms | 953 ms / 1625 ms | āˆ’4% / āˆ’10% | +| GCC 16 | parsing | 2018 ms / 3420 ms | 1522 ms / 2728 ms | āˆ’25% / āˆ’20% | +| Apple clang | `json_instance.cpp` | — | 2166 ms / 4660 ms | — | +| GCC 16 | `json_instance.cpp` | — | 5085 ms / 10616 ms | — | + +Notes: + +- The saving grows with the number of translation units that use `json`, while the instantiation translation unit is + compiled only once (and is rarely recompiled, as it does not depend on your code). +- Member function templates (such as `get()`, `parse(InputType&&)`, or `value(key, default)`) are not covered by + the explicit instantiation and are still instantiated where they are used. +- The declaration covers exactly `nlohmann::json`. Add the same lines for `nlohmann::ordered_json` + (`nlohmann::basic_json`) or your own `basic_json` specializations if you use them. + +## Use C++20 modules + +With a toolchain that supports named modules, `import nlohmann.json;` compiles the library once into a module and +avoids parsing the header in every translation unit. See [Modules](../features/modules.md) for requirements and known +issues. Module support is experimental and currently depends heavily on the compiler version. + +## Use precompiled headers + +Build systems can precompile `` together with other stable headers, for example with CMake's +[`target_precompile_headers`](https://cmake.org/cmake/help/latest/command/target_precompile_headers.html): + +```cmake +target_precompile_headers(my_target PRIVATE ) +``` + +This removes the cost of parsing the header, but not of instantiating templates in each translation unit, so it +combines well with the options above. + +## Options without effect on compile times + +Some configuration macros change what the library declares, but do not measurably change compile times: + +| Macro | Apple clang, model (`-O0` / `-O2`) | GCC 16, model (`-O0` / `-O2`) | +|------------------------------------------------------------------------|-----------------------------------:|------------------------------:| +| default | 776 ms / 846 ms | 1022 ms / 1120 ms | +| [`JSON_NO_IO`](../api/macros/json_no_io.md) | 764 ms / 836 ms | 1022 ms / 1117 ms | +| [`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md)`=0` | 763 ms / 852 ms | 1019 ms / 1106 ms | + +`JSON_USE_GLOBAL_UDLS` only controls *where* the literals are declared; to avoid their cost, use +`JSON_NO_AUTOMATIC_UDLS` instead. + +## See also + +- [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md) - do not include the user-defined string + literals automatically +- [Modules](../features/modules.md) - C++20 module support +- [Header only](index.md) - including the library diff --git a/docs/mkdocs/docs/integration/index.md b/docs/mkdocs/docs/integration/index.md index 5c3995a95..846c15fdc 100644 --- a/docs/mkdocs/docs/integration/index.md +++ b/docs/mkdocs/docs/integration/index.md @@ -45,7 +45,7 @@ Clang). You can further use file [`single_include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_fwd.hpp) -for forward declarations, and file +for forward declarations (see [Compile times](compile_times.md)), and file [`single_include/nlohmann/json_literals.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_literals.hpp) for the user-defined string literals if you define [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md). diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 3cb25ba9b..dcb94864d 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -108,6 +108,7 @@ nav: - integration/cmake.md - integration/package_managers.md - integration/pkg-config.md + - integration/compile_times.md - API Documentation: - basic_json: - 'Overview': api/basic_json/index.md From d8d47be4a5a46da22b3d8deee8e4e0b114aaf0e7 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 17:18:19 +0200 Subject: [PATCH 27/27] Fix CI on develop after merging the ready-to-merge PRs (#5754) * Keep the serializer conversion for objects whose keys cannot be converted #5591 added a test converting nlohmann::json into a basic_json whose string type cannot be constructed from std::string. That instantiates convert_iteratively(), whose members.emplace_back(next.key(), ...) needs exactly that key conversion, and broke the build of unit-alt-string. Dispatch on the key's constructibility and leave such conversions to the serializers, as the levels above the nesting bound already do (#3425). Signed-off-by: Niels Lohmann * Fix the remaining CI failures on develop - unit-wstring: with a 16-bit wchar_t (Windows), a lone surrogate is reported as the ill-formed byte 0xFF since #5704; the std::wstring expectations still had the previous . - ci_single_binaries: json_literals.hpp (#5610) and json.hpp include each other on purpose, and IWYU, not following the cycle, asks to replace json.hpp with json_fwd.hpp. Report its findings without failing the build, as already done for json.hpp. Signed-off-by: Niels Lohmann * Fix the library warnings and noexcept specifications from the merged PRs - binary_reader: rename the error_handler constructor parameter, which shadowed the member (-Wshadow, -Wshadow-field-in-constructor; #5746) - basic_json(copy_construct_tag, ...): declare it noexcept when copying the base class is (GCC 16 -Wnoexcept; #5690) - the scalar-on-left legacy comparison operators: noexcept only when converting the scalar is, like their member counterparts (#5682, #5751) - compare_leaves: use std::is_eq/is_lt/is_gt instead of comparing a std::partial_ordering with 0 (-Wzero-as-null-pointer-constant; #5686) - serializer: silence MSVC C4127 for the EnsureAscii template parameter (#5741, #5746) - clang-tidy: return the sanitized reference in binary_writer, take the key of ordered_map::find_impl by const reference (#5727), and mark the switches over parse_array_index (#5728) - ordered_map: keep for std::allocator (IWYU) Signed-off-by: Niels Lohmann * Split unit-conversions.cpp so MinGW can link it clang 18 with the MinGW linker failed to link test-conversions_cpp17 ("relocation truncated to fit: IMAGE_REL_AMD64_REL32"). As windows.yml recommends, keep the objects small by splitting the test file. Signed-off-by: Niels Lohmann * Fix the tests added by the merged PRs for all CI configurations - discard the results of dump() and from_*() in CHECK_THROWS with utils::ignore_return_value (GCC -Werror=unused-result) - give unit-bson's huge_string_t a default constructor (MSVC C2512, GCC 5, clang 3.5) - unit-disabled_exceptions: use the literals namespace when the global UDLs are off (ci_test_noglobaludls; #5700) - unit-binary_utf8_strict: expect the JSON pointer prefix with JSON_DIAGNOSTICS (#5741) - skip the tests that rely on exceptions under JSON_NOEXCEPTION (#5678, #5732) - clang-tidy and clang -Werror: static test data, CAPTURE(...);, const-correctness, use-after-move alias, unused conversion operator, a missing include Signed-off-by: Niels Lohmann * Title the macro examples and add JSON_STRICT_BINARY_UTF8 to the docset The documentation style check requires "Example: ..." titles on pages with several examples (#5741, #5591) and a docset entry for every macro page. Signed-off-by: Niels Lohmann * Regenerate BUILD.bazel and nlohmann_json.natvis Signed-off-by: Niels Lohmann #5746 added detail/output/error_handler.hpp and #5741 the json_abi_sbu8 ABI tag. * Install libidn11 for the CMake 3.5.0 binary in ci_cmake_flags Signed-off-by: Niels Lohmann #5733 moved ci_cmake_options from ubuntu:focal to ubuntu:24.04, which no longer ships libidn.so.11; the CMake 3.5.0 release binary links against it, so every ci_cmake_flags run has failed since. Install focal's libidn11 package for that matrix entry only. * Suppress Infer's false STACK_VARIABLE_ADDRESS_ESCAPE in get_impl get_impl() returns its local by value. A test added by the merged PRs instantiates it with a type Infer misreads, so ci_infer reported the 2021 code for the first time. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .github/workflows/ubuntu.yml | 7 + BUILD.bazel | 1 + cmake/ci.cmake | 10 +- docs/docset/docSet.sql | 1 + .../api/macros/json_strict_binary_utf8.md | 4 +- .../macros/json_use_implicit_conversions.md | 4 +- .../nlohmann/detail/input/binary_reader.hpp | 6 +- include/nlohmann/detail/json_pointer.hpp | 4 + .../nlohmann/detail/output/binary_writer.hpp | 6 +- include/nlohmann/detail/output/serializer.hpp | 8 + include/nlohmann/json.hpp | 56 +- include/nlohmann/ordered_map.hpp | 4 +- nlohmann_json.natvis | 3840 +++++++++++++++++ single_include/nlohmann/json.hpp | 84 +- tests/src/test_utils.hpp | 3 +- tests/src/unit-allocator.cpp | 2 +- tests/src/unit-binary_utf8_error_handler.cpp | 120 +- tests/src/unit-binary_utf8_strict.cpp | 36 +- tests/src/unit-bjdata.cpp | 2 +- tests/src/unit-bon8.cpp | 2 + tests/src/unit-bson.cpp | 4 +- tests/src/unit-cbor.cpp | 8 +- tests/src/unit-class_parser.cpp | 6 +- tests/src/unit-comparison.cpp | 4 +- ...-conversions.cpp => unit-conversions1.cpp} | 805 ---- tests/src/unit-conversions2.cpp | 868 ++++ tests/src/unit-custom-base-class.cpp | 2 +- tests/src/unit-disabled_exceptions.cpp | 3 + tests/src/unit-element_access2.cpp | 9 +- tests/src/unit-modifiers.cpp | 2 + tests/src/unit-msgpack.cpp | 2 +- tests/src/unit-ordered_map.cpp | 6 +- tests/src/unit-ubjson.cpp | 2 +- tests/src/unit-wstring.cpp | 4 +- 34 files changed, 4978 insertions(+), 947 deletions(-) rename tests/src/{unit-conversions.cpp => unit-conversions1.cpp} (52%) create mode 100644 tests/src/unit-conversions2.cpp diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index f5addb92e..1e4a72870 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -111,6 +111,13 @@ jobs: steps: - name: Install build-essential run: apt-get update ; apt-get install -y build-essential unzip wget git + # the CMake 3.5.0 release binary that ci_cmake_flags downloads links + # against libidn.so.11, which Ubuntu 24.04 no longer ships + - name: Install libidn11 for CMake 3.5.0 + if: matrix.target == 'ci_cmake_flags' + run: | + wget -q http://archive.ubuntu.com/ubuntu/pool/main/libi/libidn/libidn11_1.33-2.2ubuntu2_amd64.deb + dpkg -i libidn11_1.33-2.2ubuntu2_amd64.deb - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/BUILD.bazel b/BUILD.bazel index a9b09fd79..3dfffad1e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -57,6 +57,7 @@ cc_library( "include/nlohmann/detail/meta/type_traits.hpp", "include/nlohmann/detail/meta/void_t.hpp", "include/nlohmann/detail/output/binary_writer.hpp", + "include/nlohmann/detail/output/error_handler.hpp", "include/nlohmann/detail/output/output_adapters.hpp", "include/nlohmann/detail/output/serializer.hpp", "include/nlohmann/detail/recursion_depth_limit.hpp", diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 2aceb5afd..d0af68786 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -596,8 +596,9 @@ foreach(SRC_FILE ${SRC_FILES}) add_executable(single_${RELATIVE_SRC_FILE} EXCLUDE_FROM_ALL ${PROJECT_BINARY_DIR}/src_single/${RELATIVE_SRC_FILE}.cpp) target_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include) target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11) - if(RELATIVE_SRC_FILE STREQUAL "json") - # see below: report json.hpp's diagnostics without --error, so they do not fail the build + if(RELATIVE_SRC_FILE STREQUAL "json" OR RELATIVE_SRC_FILE STREQUAL "json_literals") + # see below: report the diagnostics of json.hpp and json_literals.hpp without --error, so they + # do not fail the build set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300) else() set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") @@ -611,7 +612,10 @@ foreach(SRC_FILE ${SRC_FILES}) # reporting its diagnostics (informational, via CXX_INCLUDE_WHAT_YOU_USE above) but exclude it # from the hard gate below so a fresh IWYU/compiler combination does not fail this target on a # nondeterministic suggestion for a header that already re-exports everything on purpose. - if(NOT RELATIVE_SRC_FILE STREQUAL "json") + # json_literals.hpp and json.hpp include each other on purpose (json.hpp includes it at its end + # unless JSON_NO_AUTOMATIC_UDLS is defined), and IWYU, not following the cycle, suggests replacing + # json.hpp with json_fwd.hpp although the literals need the complete basic_json; exclude it, too. + if(NOT RELATIVE_SRC_FILE STREQUAL "json" AND NOT RELATIVE_SRC_FILE STREQUAL "json_literals") list(APPEND single_binaries_tus src_single/${RELATIVE_SRC_FILE}.cpp) endif() endforeach() diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 09802e43e..801ade93a 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -242,6 +242,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_NO_THREAD_LOCAL', 'Macro INSERT INTO searchIndex(name, type, path) VALUES ('JSON_PRECISE_STREAM_POSITION', 'Macro', 'api/macros/json_precise_stream_position/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_LIBRARY_VERSION_CHECK', 'Macro', 'api/macros/json_skip_library_version_check/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_SKIP_UNSUPPORTED_COMPILER_CHECK', 'Macro', 'api/macros/json_skip_unsupported_compiler_check/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_BINARY_UTF8', 'Macro', 'api/macros/json_strict_binary_utf8/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_STRICT_NUL_HANDLING', 'Macro', 'api/macros/json_strict_nul_handling/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_THROW_USER', 'Macro', 'api/macros/json_throw_user/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api/macros/json_throw_user/index.html'); diff --git a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md index ed7bbe6b3..4c573d56e 100644 --- a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md +++ b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md @@ -54,7 +54,7 @@ The default value is `0` (disabled, the behavior of version 3.12.0 and earlier i ## Examples -??? example "Default behavior (macro not defined)" +??? example "Example: default behavior (macro not defined)" Without the macro, the bytes are written unchanged: @@ -70,7 +70,7 @@ The default value is `0` (disabled, the behavior of version 3.12.0 and earlier i } ``` -??? example "Opt-in check (macro defined to 1)" +??? example "Example: opt-in check (macro defined to 1)" With the macro, ill-formed UTF-8 is rejected: diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index 11bf74b22..113344412 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -44,7 +44,7 @@ By default, implicit conversions are enabled. ## Examples -??? example +??? example "Example: implicit conversion" This is an example for an implicit conversion: @@ -61,7 +61,7 @@ By default, implicit conversions are enabled. auto s = j.get(); ``` -??? example "Conversion between `basic_json` specializations" +??? example "Example: conversion between `basic_json` specializations" A `basic_json` specialization with a different string type is also no longer converted implicitly when `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`: diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 1a78a6502..d6b45909a 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -110,15 +110,15 @@ class binary_reader @param[in] adapter input adapter to read from @param[in] format the binary format to parse - @param[in] error_handler how to treat text strings and object keys that + @param[in] error_handler_ how to treat text strings and object keys that are not well-formed UTF-8; none of the supported formats requires a decoder to reject those, so the default is to @ref error_handler_t::keep them unchanged, as every binary reader did before this parameter existed */ explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json, - const error_handler_t error_handler = error_handler_t::keep) noexcept - : ia(std::move(adapter)), input_format(format), error_handler(error_handler) + const error_handler_t error_handler_ = error_handler_t::keep) noexcept + : ia(std::move(adapter)), input_format(format), error_handler(error_handler_) { (void)detail::is_sax_static_asserts {}; } diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 788af1047..30edb4a3b 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -322,6 +322,8 @@ class json_pointer typename BasicJsonType::size_type idx{}; switch (parse_array_index(s, idx)) { + // the branches differ in their messages, not after JSON_THROW's expansion + // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: @@ -685,6 +687,8 @@ class json_pointer typename BasicJsonType::size_type idx{}; switch (parse_array_index(reference_token, idx)) { + // the branches differ in their messages, not after JSON_THROW's expansion + // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index e74671db2..a4921f217 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -2260,18 +2260,18 @@ class binary_writer switch (error_handler) { case error_handler_t::keep: - return s; + return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call case error_handler_t::strict: check_utf8(s, context); - return s; + return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call case error_handler_t::replace: case error_handler_t::ignore: default: if (is_valid_utf8(s)) { - return s; + return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call } storage = sanitize_utf8(s, error_handler); return storage; diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index dcaae8055..8306440ef 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -706,6 +706,11 @@ class serializer @a ensure_ascii is a template parameter here so that the branch on it is resolved once, outside the loop; see @ref dump_escaped. */ +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push) + // EnsureAscii is a template parameter; C++11 has no if constexpr +#pragma warning(disable : 4127) // conditional expression is constant +#endif template void dump_escaped_impl(const string_t& s) { @@ -1055,6 +1060,9 @@ class serializer } } } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(pop) +#endif private: /*! diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 421feda50..3f3645524 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1048,6 +1048,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec never name it to call this constructor itself. */ basic_json(copy_construct_tag /*unused*/, const basic_json& src) + noexcept(std::is_nothrow_copy_constructible::value) : json_base_class_t(src) #if JSON_DIAGNOSTIC_POSITIONS , start_position(src.start_position) @@ -1442,7 +1443,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec be destroyed. */ template - void convert_iteratively(const BasicJsonType& val) + void convert_iteratively(const BasicJsonType& val, std::true_type /*unused*/) { using other_const_iterator = typename BasicJsonType::const_iterator; @@ -1536,21 +1537,38 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (JSON_HEDLEY_LIKELY(guard.okay())) { - // every element comes back to the converting constructor - if (val.is_object()) - { - using other_object_t = typename BasicJsonType::object_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } - else - { - using other_array_t = typename BasicJsonType::array_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } + convert_by_serializers(val); return; } - convert_iteratively(val); + // the iterative conversion needs to construct this object type's keys + // from those of @a val; if it cannot, neither can the range constructor, + // and the serializers convert @a val some other way (see #3425) + convert_iteratively(val, std::is_constructible {}); + } + + /// @brief convert the object or array @a val with the serializers; every + /// element comes back to the converting constructor + template + void convert_by_serializers(const BasicJsonType& val) + { + if (val.is_object()) + { + using other_object_t = typename BasicJsonType::object_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + else + { + using other_array_t = typename BasicJsonType::array_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + } + + /// @brief convert @a val whose keys cannot be converted, see @ref convert_structured + template + void convert_iteratively(const BasicJsonType& val, std::false_type /*unused*/) + { + convert_by_serializers(val); } @@ -1624,15 +1642,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept { const std::partial_ordering order = lhs <=> rhs; // *NOPAD* - if (order == 0) + if (std::is_eq(order)) { return compare_result::equal; } - if (order < 0) + if (std::is_lt(order)) { return compare_result::less; } - if (order > 0) + if (std::is_gt(order)) { return compare_result::greater; } @@ -2704,6 +2722,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { auto ret = ValueType(); JSONSerializer::from_json(*this, ret); + // false positive: ret is returned by value, not its address + // @infer-ignore STACK_VARIABLE_ADDRESS_ESCAPE return ret; } @@ -5058,7 +5078,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) <= rhs; } @@ -5067,7 +5087,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) >= rhs; } diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index eb521114e..2e0970726 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -12,7 +12,7 @@ #include // equal_to, less #include // initializer_list #include // input_iterator_tag, iterator_traits -#include // allocator +#include // allocator // IWYU pragma: keep #include // for operator new (placement new) #include // for out_of_range #include // forward_as_tuple @@ -78,7 +78,7 @@ private: /// @brief find the entry for @a key, for either constness of @a self /// @note the single place that performs the linear key search template - static auto find_impl(Self& self, KeyType&& key) -> decltype(self.begin()) + static auto find_impl(Self& self, const KeyType& key) -> decltype(self.begin()) { for (auto it = self.begin(); it != self.end(); ++it) { diff --git a/nlohmann_json.natvis b/nlohmann_json.natvis index ed443145e..17029f1dd 100644 --- a/nlohmann_json.natvis +++ b/nlohmann_json.natvis @@ -455,6 +455,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -755,6 +815,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -995,6 +1115,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -1175,6 +1355,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -1295,6 +1535,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -1355,6 +1655,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -1595,6 +2015,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -1775,6 +2255,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -1895,6 +2435,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -1955,6 +2555,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2135,6 +2855,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2255,6 +3035,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2315,6 +3155,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2435,6 +3395,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2495,6 +3515,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2555,6 +3695,186 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2735,6 +4055,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2855,6 +4235,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -2915,6 +4355,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3035,6 +4595,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3095,6 +4715,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3155,6 +4895,186 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3275,6 +5195,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3335,6 +5315,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3395,6 +5495,186 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3455,6 +5735,246 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3575,6 +6095,66 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3635,6 +6215,126 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3695,6 +6395,186 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3755,6 +6635,246 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3815,6 +6935,306 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + null @@ -3875,4 +7295,424 @@ + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + + + + null + {*(m_data.m_value.object)} + {*(m_data.m_value.array)} + {*(m_data.m_value.string)} + {m_data.m_value.boolean} + {m_data.m_value.number_integer} + {m_data.m_value.number_unsigned} + {m_data.m_value.number_float} + discarded + + + *(m_data.m_value.object),view(simple) + + + *(m_data.m_value.array),view(simple) + + + + + + + {second} + + second + + + diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 511401699..75684866e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -13696,15 +13696,15 @@ class binary_reader @param[in] adapter input adapter to read from @param[in] format the binary format to parse - @param[in] error_handler how to treat text strings and object keys that + @param[in] error_handler_ how to treat text strings and object keys that are not well-formed UTF-8; none of the supported formats requires a decoder to reject those, so the default is to @ref error_handler_t::keep them unchanged, as every binary reader did before this parameter existed */ explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json, - const error_handler_t error_handler = error_handler_t::keep) noexcept - : ia(std::move(adapter)), input_format(format), error_handler(error_handler) + const error_handler_t error_handler_ = error_handler_t::keep) noexcept + : ia(std::move(adapter)), input_format(format), error_handler(error_handler_) { (void)detail::is_sax_static_asserts {}; } @@ -19998,6 +19998,8 @@ class json_pointer typename BasicJsonType::size_type idx{}; switch (parse_array_index(s, idx)) { + // the branches differ in their messages, not after JSON_THROW's expansion + // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: @@ -20361,6 +20363,8 @@ class json_pointer typename BasicJsonType::size_type idx{}; switch (parse_array_index(reference_token, idx)) { + // the branches differ in their messages, not after JSON_THROW's expansion + // NOLINTNEXTLINE(bugprone-branch-clone) case array_index_status::leading_zero: JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: @@ -23485,18 +23489,18 @@ class binary_writer switch (error_handler) { case error_handler_t::keep: - return s; + return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call case error_handler_t::strict: check_utf8(s, context); - return s; + return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call case error_handler_t::replace: case error_handler_t::ignore: default: if (is_valid_utf8(s)) { - return s; + return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call } storage = sanitize_utf8(s, error_handler); return storage; @@ -25681,6 +25685,11 @@ class serializer @a ensure_ascii is a template parameter here so that the branch on it is resolved once, outside the loop; see @ref dump_escaped. */ +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push) + // EnsureAscii is a template parameter; C++11 has no if constexpr +#pragma warning(disable : 4127) // conditional expression is constant +#endif template void dump_escaped_impl(const string_t& s) { @@ -26030,6 +26039,9 @@ class serializer } } } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(pop) +#endif private: /*! @@ -26612,7 +26624,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // equal_to, less #include // initializer_list #include // input_iterator_tag, iterator_traits -#include // allocator +#include // allocator // IWYU pragma: keep #include // for operator new (placement new) #include // for out_of_range #include // forward_as_tuple @@ -26681,7 +26693,7 @@ private: /// @brief find the entry for @a key, for either constness of @a self /// @note the single place that performs the linear key search template - static auto find_impl(Self& self, KeyType&& key) -> decltype(self.begin()) + static auto find_impl(Self& self, const KeyType& key) -> decltype(self.begin()) { for (auto it = self.begin(); it != self.end(); ++it) { @@ -27985,6 +27997,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec never name it to call this constructor itself. */ basic_json(copy_construct_tag /*unused*/, const basic_json& src) + noexcept(std::is_nothrow_copy_constructible::value) : json_base_class_t(src) #if JSON_DIAGNOSTIC_POSITIONS , start_position(src.start_position) @@ -28379,7 +28392,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec be destroyed. */ template - void convert_iteratively(const BasicJsonType& val) + void convert_iteratively(const BasicJsonType& val, std::true_type /*unused*/) { using other_const_iterator = typename BasicJsonType::const_iterator; @@ -28473,21 +28486,38 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (JSON_HEDLEY_LIKELY(guard.okay())) { - // every element comes back to the converting constructor - if (val.is_object()) - { - using other_object_t = typename BasicJsonType::object_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } - else - { - using other_array_t = typename BasicJsonType::array_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } + convert_by_serializers(val); return; } - convert_iteratively(val); + // the iterative conversion needs to construct this object type's keys + // from those of @a val; if it cannot, neither can the range constructor, + // and the serializers convert @a val some other way (see #3425) + convert_iteratively(val, std::is_constructible {}); + } + + /// @brief convert the object or array @a val with the serializers; every + /// element comes back to the converting constructor + template + void convert_by_serializers(const BasicJsonType& val) + { + if (val.is_object()) + { + using other_object_t = typename BasicJsonType::object_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + else + { + using other_array_t = typename BasicJsonType::array_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + } + + /// @brief convert @a val whose keys cannot be converted, see @ref convert_structured + template + void convert_iteratively(const BasicJsonType& val, std::false_type /*unused*/) + { + convert_by_serializers(val); } @@ -28561,15 +28591,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static compare_result compare_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept { const std::partial_ordering order = lhs <=> rhs; // *NOPAD* - if (order == 0) + if (std::is_eq(order)) { return compare_result::equal; } - if (order < 0) + if (std::is_lt(order)) { return compare_result::less; } - if (order > 0) + if (std::is_gt(order)) { return compare_result::greater; } @@ -29641,6 +29671,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { auto ret = ValueType(); JSONSerializer::from_json(*this, ret); + // false positive: ret is returned by value, not its address + // @infer-ignore STACK_VARIABLE_ADDRESS_ESCAPE return ret; } @@ -31995,7 +32027,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) <= rhs; } @@ -32004,7 +32036,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) { return basic_json(lhs) >= rhs; } diff --git a/tests/src/test_utils.hpp b/tests/src/test_utils.hpp index b2a381fb0..8f7bd06a0 100644 --- a/tests/src/test_utils.hpp +++ b/tests/src/test_utils.hpp @@ -10,7 +10,8 @@ #include // uint8_t #include // size_t -#include // ifstream, istreambuf_iterator, ios +#include // ifstream, ios +#include // istream_iterator #include // vector namespace utils diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index fbdfaa351..bc283ab28 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -567,7 +567,7 @@ struct allocator_no_forward : std::allocator { allocator_no_forward() = default; template - allocator_no_forward(allocator_no_forward /*unused*/) {} + allocator_no_forward(const allocator_no_forward& /*unused*/) {} template struct rebind diff --git a/tests/src/unit-binary_utf8_error_handler.cpp b/tests/src/unit-binary_utf8_error_handler.cpp index 85505bffb..c3f2435fd 100644 --- a/tests/src/unit-binary_utf8_error_handler.cpp +++ b/tests/src/unit-binary_utf8_error_handler.cpp @@ -7,6 +7,7 @@ // SPDX-License-Identifier: MIT #include "doctest_compatibility.h" +#include "test_utils.hpp" #include using nlohmann::json; @@ -25,18 +26,27 @@ struct ill_formed_case // RFC 3629 ill-formed sequences used throughout this file, plus one // well-formed sequence for contrast -const std::vector ill_formed_cases = +std::vector ill_formed_cases() { - {"overlong", "\xC0\xAE"}, - {"lone_0xFF", "\xFF"}, - {"truncated", "\xE2\x82"}, - {"surrogate", "\xED\xA0\x80"}, -}; + return + { + {"overlong", "\xC0\xAE"}, + {"lone_0xFF", "\xFF"}, + {"truncated", "\xE2\x82"}, + {"surrogate", "\xED\xA0\x80"}, + }; +} -const std::string valid_sequence = "\xC3\xA9"; // U+00E9, "Ć©" +std::string valid_sequence() +{ + return "\xC3\xA9"; // U+00E9, "Ć©" +} using eh = json::error_handler_t; -const std::vector all_handlers = {eh::strict, eh::replace, eh::ignore, eh::keep}; +std::vector all_handlers() +{ + return {eh::strict, eh::replace, eh::ignore, eh::keep}; +} // what dump()+parse() produces for a sanitizing error_handler; this is the // ground truth every binary writer/reader is checked against @@ -51,9 +61,9 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") { SECTION("writers: string value") { - for (const auto& c : ill_formed_cases) + for (const auto& c : ill_formed_cases()) { - CAPTURE(c.name); + CAPTURE(c.name) const json jval = c.bytes; CHECK_THROWS_AS(json::to_cbor(jval, eh::strict), json::type_error&); @@ -71,7 +81,7 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") eh::replace, eh::ignore }) { - CAPTURE(static_cast(h)); + CAPTURE(static_cast(h)) const std::string expected = dump_and_parse(c.bytes, h); CHECK(json::from_cbor(json::to_cbor(jval, h)).get() == expected); @@ -103,9 +113,9 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") SECTION("writers: object key") { - for (const auto& c : ill_formed_cases) + for (const auto& c : ill_formed_cases()) { - CAPTURE(c.name); + CAPTURE(c.name) json jobj; jobj[c.bytes] = 1; @@ -120,7 +130,7 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") eh::replace, eh::ignore }) { - CAPTURE(static_cast(h)); + CAPTURE(static_cast(h)) const std::string expected = dump_and_parse(c.bytes, h); CHECK(json::from_cbor(json::to_cbor(jobj, h)).begin().key() == expected); @@ -141,9 +151,9 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") SECTION("readers: string value") { - for (const auto& c : ill_formed_cases) + for (const auto& c : ill_formed_cases()) { - CAPTURE(c.name); + CAPTURE(c.name) // bytes produced the lenient (keep) way, as any binary reader // accepted them before this parameter existed @@ -166,15 +176,15 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") CHECK(json::from_bson(bson_bytes)["k"].get() == c.bytes); // strict: parse_error.113, discarded (not thrown) when allow_exceptions is false - CHECK_THROWS_AS(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict)), json::parse_error&); CHECK(json::from_cbor(cbor_bytes, true, false, json::cbor_tag_handler_t::error, eh::strict).is_discarded()); - CHECK_THROWS_AS(json::from_msgpack(msgpack_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_msgpack(msgpack_bytes, true, true, eh::strict)), json::parse_error&); CHECK(json::from_msgpack(msgpack_bytes, true, false, eh::strict).is_discarded()); - CHECK_THROWS_AS(json::from_ubjson(ubjson_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_ubjson(ubjson_bytes, true, true, eh::strict)), json::parse_error&); CHECK(json::from_ubjson(ubjson_bytes, true, false, eh::strict).is_discarded()); - CHECK_THROWS_AS(json::from_bjdata(bjdata_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_bjdata(bjdata_bytes, true, true, eh::strict)), json::parse_error&); CHECK(json::from_bjdata(bjdata_bytes, true, false, eh::strict).is_discarded()); - CHECK_THROWS_AS(json::from_bson(bson_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_bson(bson_bytes, true, true, eh::strict)), json::parse_error&); CHECK(json::from_bson(bson_bytes, true, false, eh::strict).is_discarded()); // replace / ignore: match what dump() would have sanitized the same bytes to @@ -183,7 +193,7 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") eh::replace, eh::ignore }) { - CAPTURE(static_cast(h)); + CAPTURE(static_cast(h)) const std::string expected = dump_and_parse(c.bytes, h); CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).get() == expected); @@ -197,9 +207,9 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") SECTION("readers: object key") { - for (const auto& c : ill_formed_cases) + for (const auto& c : ill_formed_cases()) { - CAPTURE(c.name); + CAPTURE(c.name) json jobj; jobj[c.bytes] = 1; @@ -215,18 +225,18 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") CHECK(json::from_bjdata(bjdata_bytes).begin().key() == c.bytes); CHECK(json::from_bson(bson_bytes).begin().key() == c.bytes); - CHECK_THROWS_AS(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict), json::parse_error&); - CHECK_THROWS_AS(json::from_msgpack(msgpack_bytes, true, true, eh::strict), json::parse_error&); - CHECK_THROWS_AS(json::from_ubjson(ubjson_bytes, true, true, eh::strict), json::parse_error&); - CHECK_THROWS_AS(json::from_bjdata(bjdata_bytes, true, true, eh::strict), json::parse_error&); - CHECK_THROWS_AS(json::from_bson(bson_bytes, true, true, eh::strict), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, eh::strict)), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_msgpack(msgpack_bytes, true, true, eh::strict)), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_ubjson(ubjson_bytes, true, true, eh::strict)), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_bjdata(bjdata_bytes, true, true, eh::strict)), json::parse_error&); + CHECK_THROWS_AS(utils::ignore_return_value(json::from_bson(bson_bytes, true, true, eh::strict)), json::parse_error&); for (const auto h : { eh::replace, eh::ignore }) { - CAPTURE(static_cast(h)); + CAPTURE(static_cast(h)) const std::string expected = dump_and_parse(c.bytes, h); CHECK(json::from_cbor(cbor_bytes, true, true, json::cbor_tag_handler_t::error, h).begin().key() == expected); @@ -240,30 +250,30 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") SECTION("well-formed UTF-8 is unaffected by error_handler") { - const json jval = valid_sequence; + const json jval = valid_sequence(); json jobj; - jobj[valid_sequence] = valid_sequence; + jobj[valid_sequence()] = valid_sequence(); - for (const auto h : all_handlers) + for (const auto h : all_handlers()) { - CAPTURE(static_cast(h)); + CAPTURE(static_cast(h)) - CHECK(json::from_cbor(json::to_cbor(jval, h)).get() == valid_sequence); - CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get() == valid_sequence); - CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get() == valid_sequence); - CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get() == valid_sequence); - CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == valid_sequence); + CHECK(json::from_cbor(json::to_cbor(jval, h)).get() == valid_sequence()); + CHECK(json::from_msgpack(json::to_msgpack(jval, h)).get() == valid_sequence()); + CHECK(json::from_ubjson(json::to_ubjson(jval, false, false, h)).get() == valid_sequence()); + CHECK(json::from_bjdata(json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, h)).get() == valid_sequence()); + CHECK(json::from_bson(json::to_bson(jobj, h)).begin().key() == valid_sequence()); - CHECK(json::from_cbor(json::to_cbor(jval, eh::keep), true, true, json::cbor_tag_handler_t::error, h).get() == valid_sequence); - CHECK(json::from_msgpack(json::to_msgpack(jval), true, true, h).get() == valid_sequence); + CHECK(json::from_cbor(json::to_cbor(jval, eh::keep), true, true, json::cbor_tag_handler_t::error, h).get() == valid_sequence()); + CHECK(json::from_msgpack(json::to_msgpack(jval), true, true, h).get() == valid_sequence()); } } SECTION("dump() with error_handler_t::keep writes raw bytes as is") { - for (const auto& c : ill_formed_cases) + for (const auto& c : ill_formed_cases()) { - CAPTURE(c.name); + CAPTURE(c.name) const json jval = c.bytes; const std::string dumped = jval.dump(-1, ' ', false, eh::keep); @@ -276,10 +286,10 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") // well-formed characters around an ill-formed sequence are still // escaped as usual under ensure_ascii - const json mixed = valid_sequence + ill_formed_cases[1].bytes; // "Ć©" + lone 0xFF + const json mixed = valid_sequence() + ill_formed_cases()[1].bytes; // "Ć©" + lone 0xFF const std::string dumped_mixed = mixed.dump(-1, ' ', true, eh::keep); CHECK(dumped_mixed.find("\\u00e9") != std::string::npos); - CHECK(dumped_mixed.find(ill_formed_cases[1].bytes) != std::string::npos); + CHECK(dumped_mixed.find(ill_formed_cases()[1].bytes) != std::string::npos); // the byte that ends an ill-formed sequence is read again, so a quote, // a backslash, or a control character after it is still escaped, and @@ -289,7 +299,7 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") false, true }) { - CAPTURE(ensure_ascii); + CAPTURE(ensure_ascii) CHECK(json("\xC3\"").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\"\""); CHECK(json("\xC3\\").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\\\\""); CHECK(json("\xC3\n").dump(-1, ' ', ensure_ascii, eh::keep) == "\"\xC3\\n\""); @@ -303,12 +313,12 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") SECTION("to_msgpack defaults to keep; to_bon8 is not affected by error_handler") { - const json jval = ill_formed_cases[1].bytes; // lone 0xFF + const json jval = ill_formed_cases()[1].bytes; // lone 0xFF // to_msgpack's error_handler defaults to keep, as MessagePack's spec // allows any bytes in a str, so the bytes are passed through CHECK(json::to_msgpack(jval) == json::to_msgpack(jval, eh::keep)); - CHECK(json::from_msgpack(json::to_msgpack(jval)).get() == ill_formed_cases[1].bytes); + CHECK(json::from_msgpack(json::to_msgpack(jval)).get() == ill_formed_cases()[1].bytes); // the diagnostics context of an ill-formed key is the object json jobj; @@ -322,14 +332,14 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") SECTION("allow_exceptions=false with error_handler_t::strict discards the value") { - const auto bytes = json::to_cbor(json(ill_formed_cases[0].bytes), eh::keep); + const auto bytes = json::to_cbor(json(ill_formed_cases()[0].bytes), eh::keep); const json result = json::from_cbor(bytes, true, false, json::cbor_tag_handler_t::error, eh::strict); CHECK(result.is_discarded()); } SECTION("default parameters are unchanged") { - const json jval = ill_formed_cases[0].bytes; + const json jval = ill_formed_cases()[0].bytes; // to_*: the default error_handler is keep, so ill-formed bytes are // written unchanged, exactly as in release 3.12.0 (it is strict only @@ -347,16 +357,16 @@ TEST_CASE("UTF-8 error_handler for the binary readers and writers") // from_*: the default error_handler is keep, so ill-formed bytes are // still accepted unchanged, exactly as in release 3.12.0 const auto cbor_bytes = json::to_cbor(jval, eh::keep); - CHECK(json::from_cbor(cbor_bytes).get() == ill_formed_cases[0].bytes); + CHECK(json::from_cbor(cbor_bytes).get() == ill_formed_cases()[0].bytes); const auto ubjson_bytes = json::to_ubjson(jval, false, false, eh::keep); - CHECK(json::from_ubjson(ubjson_bytes).get() == ill_formed_cases[0].bytes); + CHECK(json::from_ubjson(ubjson_bytes).get() == ill_formed_cases()[0].bytes); const auto bjdata_bytes = json::to_bjdata(jval, false, false, json::bjdata_version_t::draft2, eh::keep); - CHECK(json::from_bjdata(bjdata_bytes).get() == ill_formed_cases[0].bytes); + CHECK(json::from_bjdata(bjdata_bytes).get() == ill_formed_cases()[0].bytes); const auto msgpack_bytes = json::to_msgpack(jval); - CHECK(json::from_msgpack(msgpack_bytes).get() == ill_formed_cases[0].bytes); + CHECK(json::from_msgpack(msgpack_bytes).get() == ill_formed_cases()[0].bytes); json bson_obj; bson_obj["k"] = jval; const auto bson_bytes = json::to_bson(bson_obj, eh::keep); - CHECK(json::from_bson(bson_bytes)["k"].get() == ill_formed_cases[0].bytes); + CHECK(json::from_bson(bson_bytes)["k"].get() == ill_formed_cases()[0].bytes); } } diff --git a/tests/src/unit-binary_utf8_strict.cpp b/tests/src/unit-binary_utf8_strict.cpp index 2ac8aabf4..96d96e61c 100644 --- a/tests/src/unit-binary_utf8_strict.cpp +++ b/tests/src/unit-binary_utf8_strict.cpp @@ -83,21 +83,45 @@ TEST_CASE("JSON_STRICT_BINARY_UTF8 (see #5529, #5651)") // any bytes reach the output adapter (the BSON document length // prefix must be known up front, so nothing is written incrementally) std::vector out{0x42}; // a sentinel byte the writer must not touch - CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}, nlohmann::detail::output_adapter(out)), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}, nlohmann::detail::output_adapter(out)), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}, nlohmann::detail::output_adapter(out)), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#endif CHECK(out == std::vector {0x42}); - CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xFF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#endif // a truncated multi-byte sequence - CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC3"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC3"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xC3", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC3"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&); +#endif // an encoded surrogate half (U+D800) - CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xED", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&); +#endif // an overlong encoding of '.' - CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] (/s) invalid UTF-8 byte at index 0: 0xC0", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(json {{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&); +#endif // an object key with ill-formed UTF-8 is rejected as well; unlike // the reader (which never validates element names), the writer // checks both string values and object keys - CHECK_THROWS_WITH_AS(json::to_bson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(json {{"\xFF", 1}}), "[json.exception.type_error.316] (/\xFF) invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(json {{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&); +#endif } SECTION("an explicit error_handler overrides the default") diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index f34df1717..0098541cf 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3921,7 +3921,7 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") CHECK_NOTHROW(j = json::from_bjdata(v)); REQUIRE(j.is_string()); CHECK(j.get_ref() == std::string("\xc0\xae")); - CHECK_THROWS_AS(j.dump(), json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); CHECK(json::from_bjdata(json::to_bjdata(j)) == j); // the same bytes as an object key round-trip as well diff --git a/tests/src/unit-bon8.cpp b/tests/src/unit-bon8.cpp index a751d7dbe..491189867 100644 --- a/tests/src/unit-bon8.cpp +++ b/tests/src/unit-bon8.cpp @@ -786,6 +786,7 @@ TEST_CASE("Parse BON8 directly from a file using iterator and sentinel") CHECK((parsed.is_object() || parsed.is_array())); } +#if !defined(JSON_NOEXCEPTION) // corpus values that do not survive the round trip are skipped by catching the exception TEST_CASE("BON8 round-trip invariants") { // This checks what the parse_bon8_fuzzer driver checks (see @@ -818,6 +819,7 @@ TEST_CASE("BON8 round-trip invariants") CHECK(json::to_bon8(j2) == vec); } } +#endif TEST_CASE("BON8 roundtrips" * doctest::skip()) { diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 92a14e6fe..17819dc65 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -62,6 +62,8 @@ class huge_string_t : public std::string { public: using std::string::string; + // inheriting std::string's constructors does not inherit its default constructor + huge_string_t() = default; huge_string_t(const std::string& s) : std::string(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // returns a copy of @a s whose size() pretends to be huge @@ -174,7 +176,7 @@ TEST_CASE("BSON") REQUIRE(j.contains("s")); CHECK(j["s"].get_ref() == std::string("\xc0\xae")); // dump() still requires valid UTF-8 and throws for such a value - CHECK_THROWS_AS(j.dump(), json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); // to_bson() writes the bytes back unchanged, as before 3.13.0, // unless JSON_STRICT_BINARY_UTF8 is enabled (see unit-binary_utf8_strict.cpp) CHECK(json::from_bson(json::to_bson(j)) == j); diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 633f50fea..f3f4301e5 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -1820,7 +1820,7 @@ TEST_CASE("CBOR") // dump() still requires valid UTF-8 and throws for such a value, // unless an error handler that replaces or ignores the bytes is // passed - CHECK_THROWS_AS(j_value.dump(), json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(j_value.dump()), json::type_error&); // to_cbor() writes the bytes back unchanged, as before 3.13.0, // unless JSON_STRICT_BINARY_UTF8 is enabled (see unit-binary_utf8_strict.cpp) CHECK(json::from_cbor(json::to_cbor(j_value)) == j_value); @@ -1878,13 +1878,13 @@ TEST_CASE("CBOR") // a truncated code point is kept as is CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x7f, 0x61, 0xc3, 0xff}))); CHECK(_ == "\xc3"); - CHECK_THROWS_AS(_.dump(), json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(_.dump()), json::type_error&); CHECK(json::from_cbor(json::to_cbor(_)) == _); // an ill-formed later chunk is kept after valid ones CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc0, 0xae, 0xff}))); CHECK(_ == "\xc3\xa9\xc0\xae"); - CHECK_THROWS_AS(_.dump(), json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(_.dump()), json::type_error&); // valid multi-byte chunks are accepted CHECK(json::from_cbor(std::vector({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6"); @@ -2390,6 +2390,7 @@ TEST_CASE("issue #5405 - array reserve for definite-length CBOR arrays") } } +#if !defined(JSON_NOEXCEPTION) // corpus values that do not survive the round trip are skipped by catching the exception TEST_CASE("CBOR round-trip invariants") { // This checks what the parse_cbor_fuzzer driver checks (see @@ -2422,6 +2423,7 @@ TEST_CASE("CBOR round-trip invariants") CHECK(json::to_cbor(j2) == vec); } } +#endif TEST_CASE("CBOR roundtrips" * doctest::skip()) { diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 0ae5e382b..f2de97ea3 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -2704,9 +2704,9 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX") CHECK(b["b"].end_pos() == nested_end); // the moved-from value is reset to a null and reports npos - CHECK(a.is_null()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move) - CHECK(a.start_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move) - CHECK(a.end_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move) + CHECK(a.is_null()); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + CHECK(a.start_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + CHECK(a.end_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) } SECTION("swap() exchanges positions along with values") diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index 15715fec2..b9257cb17 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -969,7 +969,7 @@ TEST_CASE("copying an object preserves its comparator's state") for (const std::size_t depth : std::vector {0, 127, 128, 200}) { - CAPTURE(depth); + CAPTURE(depth) key_case_json original = object; for (std::size_t i = 0; i < depth; ++i) @@ -1138,7 +1138,7 @@ TEST_CASE("operator<=> of binary values with a different subtype does not depend // and must still agree with the levels that do for (const std::size_t depth : std::vector {0, 127, 128, 200}) { - CAPTURE(depth); + CAPTURE(depth) const json x = deep(a, depth); const json y = deep(b, depth); CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD* diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions1.cpp similarity index 52% rename from tests/src/unit-conversions.cpp rename to tests/src/unit-conversions1.cpp index 5f83a9253..3d39e10c9 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions1.cpp @@ -15,11 +15,6 @@ #include "doctest_compatibility.h" -// skip tests if JSON_DisableEnumSerialization=ON (#4384) -#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1) - #define SKIP_TESTS_FOR_ENUM_SERIALIZATION -#endif - #define JSON_TESTS_PRIVATE #include using nlohmann::json; @@ -1259,808 +1254,8 @@ TEST_CASE("value conversion") } } #endif - - SECTION("get a binary value (explicit)") - { - json::binary_t const n_reference{{1, 2, 3}}; - json j(n_reference); - - SECTION("binary_t") - { - json::binary_t const b = j.get(); - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - - SECTION("get_binary()") - { - SECTION("non-const") - { - auto& b = j.get_binary(); - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - - SECTION("non-const") - { - const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization) - const auto& b = j_const.get_binary(); - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - } - - SECTION("exception in case of a non-string type") - { - json j_null(json::value_t::null); - json j_object(json::value_t::object); - json j_array(json::value_t::array); - json j_string(json::value_t::string); - json j_boolean(json::value_t::boolean); - const json j_null_const(json::value_t::null); - const json j_object_const(json::value_t::object); - const json j_array_const(json::value_t::array); - const json j_string_const(json::value_t::string); - const json j_boolean_const(json::value_t::boolean); - - CHECK_THROWS_WITH_AS(j_null.get(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object.get(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array.get(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string.get(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean.get(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - - CHECK_THROWS_WITH_AS(j_null_const.get(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object_const.get(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array_const.get(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string_const.get(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean_const.get(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - - CHECK_THROWS_WITH_AS(j_null.get_binary(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object.get_binary(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array.get_binary(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string.get_binary(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean.get_binary(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - - CHECK_THROWS_WITH_AS(j_null_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is null", - json::type_error&); - CHECK_THROWS_WITH_AS(j_object_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is object", - json::type_error&); - CHECK_THROWS_WITH_AS(j_array_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is array", - json::type_error&); - CHECK_THROWS_WITH_AS(j_string_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is string", - json::type_error&); - CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(), - "[json.exception.type_error.302] type must be binary, but is boolean", - json::type_error&); - } - } - -#if JSON_USE_IMPLICIT_CONVERSIONS - SECTION("get a binary value (implicit)") - { - json::binary_t const n_reference{{1, 2, 3}}; - json const j(n_reference); - - SECTION("binary_t") - { - json::binary_t const b = j; - CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); - } - } -#endif - -#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION - SECTION("get an enum") - { - enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class) - enum class cpp_enum { value_1, value_2 }; - - CHECK(json(value_1).get() == value_1); - CHECK(json(cpp_enum::value_1).get() == cpp_enum::value_1); - } - - SECTION("get an enum with underlying type bool (#5671)") - { - enum class bool_enum : bool { off, on }; - - CHECK(json(bool_enum::off).get() == bool_enum::off); - CHECK(json(bool_enum::on).get() == bool_enum::on); - } -#endif - - SECTION("more involved conversions") - { - SECTION("object-like STL containers") - { - json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}}; - json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}}; - json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}; - json const j4 = {{"one", true}, {"two", false}, {"three", true}}; - json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}; - - SECTION("std::map") - { - CHECK(j1.get>() == (std::map {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::map {{"one", true}, {"two", false}, {"three", true}})); - CHECK(j5.get>() == (std::map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - } - - SECTION("std::unordered_map") - { - CHECK(j1.get>() == (std::unordered_map {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::unordered_map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::unordered_map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::unordered_map {{"one", true}, {"two", false}, {"three", true}})); - const auto m5 = j5.get>(); - CHECK(m5 == (std::unordered_map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - CHECK(m5.at("one") == "eins"); - } - - SECTION("reserve is called on containers that support it (#5406)") - { - // build a larger object so that a missing/incorrect reserve() - // call would be more likely to corrupt or drop elements - json j_large; - for (int i = 0; i < 100; ++i) - { - j_large[std::to_string(i)] = i; - } - - SECTION("std::unordered_map (supports reserve)") - { - const auto m = j_large.get>(); - CHECK(m.size() == 100); - for (int i = 0; i < 100; ++i) - { - CHECK(m.at(std::to_string(i)) == i); - } - } - - SECTION("std::map (no reserve, fallback path)") - { - const auto m = j_large.get>(); - CHECK(m.size() == 100); - for (int i = 0; i < 100; ++i) - { - CHECK(m.at(std::to_string(i)) == i); - } - } - } - - SECTION("std::multimap") - { - CHECK(j1.get>() == (std::multimap {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::multimap {{"one", true}, {"two", false}, {"three", true}})); - const auto m5 = j5.get>(); - CHECK(m5 == (std::multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - CHECK(m5.find("one")->second == "eins"); - } - - SECTION("std::unordered_multimap") - { - CHECK(j1.get>() == (std::unordered_multimap {{"one", 1}, {"two", 2}, {"three", 3}})); - CHECK(j2.get>() == (std::unordered_multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); - CHECK(j3.get>() == (std::unordered_multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); - CHECK(j4.get>() == (std::unordered_multimap {{"one", true}, {"two", false}, {"three", true}})); - const auto m5 = j5.get>(); - CHECK(m5 == (std::unordered_multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); - CHECK(m5.find("one")->second == "eins"); - } - - SECTION("exception in case of a non-object type") - { - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be object, but is null", json::type_error&); - } - } - - SECTION("array-like STL containers") - { - json const j1 = {1, 2, 3, 4}; - json const j2 = {1u, 2u, 3u, 4u}; - json const j3 = {1.2, 2.3, 3.4, 4.5}; - json const j4 = {true, false, true}; - json const j5 = {"one", "two", "three"}; - - SECTION("std::list") - { - CHECK(j1.get>() == (std::list {1, 2, 3, 4})); - CHECK(j2.get>() == (std::list {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::list {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::list {true, false, true})); - CHECK(j5.get>() == (std::list {"one", "two", "three"})); - } - - SECTION("std::forward_list") - { - CHECK(j1.get>() == (std::forward_list {1, 2, 3, 4})); - CHECK(j2.get>() == (std::forward_list {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::forward_list {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::forward_list {true, false, true})); - CHECK(j5.get>() == (std::forward_list {"one", "two", "three"})); - } - - SECTION("std::array") - { - CHECK(j1.get>() == (std::array {{1, 2, 3, 4}})); - // only the first 3 elements of j2 are converted, since the target array is smaller - CHECK(j2.get>() == (std::array {{1u, 2u, 3u}})); - CHECK(j3.get>() == (std::array {{1.2, 2.3, 3.4, 4.5}})); - CHECK(j4.get>() == (std::array {{true, false, true}})); - CHECK(j5.get>() == (std::array {{"one", "two", "three"}})); - - SECTION("std::array is larger than JSON") - { - std::array arr6 = {{1, 2, 3, 4, 5, 6}}; - CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] " - "array index 4 is out of range", json::out_of_range&); - } - - SECTION("std::array is smaller than JSON") - { - std::array arr2 = {{8, 9}}; - j1.get_to(arr2); - CHECK(arr2[0] == 1); - CHECK(arr2[1] == 2); - } - } - - SECTION("std::valarray") - { - // valarray has no operator== that returns bool, so compare via a vector copy - const auto v1 = j1.get>(); - CHECK((std::vector(std::begin(v1), std::end(v1)) == std::vector {1, 2, 3, 4})); - const auto v2 = j2.get>(); - CHECK((std::vector(std::begin(v2), std::end(v2)) == std::vector {1u, 2u, 3u, 4u})); - const auto v3 = j3.get>(); - CHECK((std::vector(std::begin(v3), std::end(v3)) == std::vector {1.2, 2.3, 3.4, 4.5})); - const auto v4 = j4.get>(); - CHECK((std::vector(std::begin(v4), std::end(v4)) == std::vector {true, false, true})); - const auto v5 = j5.get>(); - CHECK((std::vector(std::begin(v5), std::end(v5)) == std::vector {"one", "two", "three"})); - } - - SECTION("std::vector") - { - CHECK(j1.get>() == (std::vector {1, 2, 3, 4})); - CHECK(j2.get>() == (std::vector {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::vector {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::vector {true, false, true})); - CHECK(j5.get>() == (std::vector {"one", "two", "three"})); - } - - SECTION("std::deque") - { - CHECK(j1.get>() == (std::deque {1, 2, 3, 4})); - CHECK(j2.get>() == (std::deque {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::deque {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::deque {true, false, true})); - CHECK(j5.get>() == (std::deque {"one", "two", "three"})); - } - - SECTION("std::set") - { - CHECK(j1.get>() == (std::set {1, 2, 3, 4})); - CHECK(j2.get>() == (std::set {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::set {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::set {true, false, true})); - CHECK(j5.get>() == (std::set {"one", "two", "three"})); - } - - SECTION("std::unordered_set") - { - CHECK(j1.get>() == (std::unordered_set {1, 2, 3, 4})); - CHECK(j2.get>() == (std::unordered_set {1u, 2u, 3u, 4u})); - CHECK(j3.get>() == (std::unordered_set {1.2, 2.3, 3.4, 4.5})); - CHECK(j4.get>() == (std::unordered_set {true, false, true})); - CHECK(j5.get>() == (std::unordered_set {"one", "two", "three"})); - } - - SECTION("std::map (array of pairs)") - { - const std::map m{{0, 1}, {1, 2}, {2, 3}}; - json const j6 = m; - - auto m2 = j6.get>(); - CHECK(m == m2); - - json const j7 = {0, 1, 2, 3}; - json const j8 = 2; -#if JSON_DIAGNOSTICS - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] (/0) type must be array, " - "but is number", json::type_error&); -#else - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); -#endif - CHECK_THROWS_WITH_AS((j8.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); - - SECTION("superfluous entries") - { - json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; - m2 = j9.get>(); - CHECK(m == m2); - } - } - - SECTION("std::unordered_map (array of pairs)") - { - const std::unordered_map m{{0, 1}, {1, 2}, {2, 3}}; - json const j6 = m; - - auto m2 = j6.get>(); - CHECK(m == m2); - - json const j7 = {0, 1, 2, 3}; - json const j8 = 2; -#if JSON_DIAGNOSTICS - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] (/0) type must be array, " - "but is number", json::type_error&); -#else - CHECK_THROWS_WITH_AS((j7.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); -#endif - CHECK_THROWS_WITH_AS((j8.get>()), - "[json.exception.type_error.302] type must be array, " - "but is number", json::type_error&); - - SECTION("superfluous entries") - { - json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; - m2 = j9.get>(); - CHECK(m == m2); - } - } - - SECTION("exception in case of a non-object type") - { - // does type really must be an array? or it rather must not be null? - // that's what I thought when other test like this one broke - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - CHECK_THROWS_WITH_AS( - (json().get>()), - "[json.exception.type_error.302] type must be array, but is null", json::type_error&); - } - } - } } -enum class cards {kreuz, pik, herz, karo}; - -// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM(cards, -{ - {cards::kreuz, "kreuz"}, - {cards::pik, "pik"}, - {cards::pik, "puk"}, // second entry for cards::puk; will not be used - {cards::herz, "herz"}, - {cards::karo, "karo"} -}) - -enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) -{ - TS_STOPPED, - TS_RUNNING, - TS_COMPLETED, - TS_INVALID = -1, -}; - -// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, -{ - {TS_INVALID, nullptr}, - {TS_STOPPED, "stopped"}, - {TS_RUNNING, "running"}, - {TS_COMPLETED, "completed"}, -}) - -TEST_CASE("JSON to enum mapping") -{ - SECTION("enum class") - { - // enum -> json - CHECK(json(cards::kreuz) == "kreuz"); - CHECK(json(cards::pik) == "pik"); - CHECK(json(cards::herz) == "herz"); - CHECK(json(cards::karo) == "karo"); - - // json -> enum - CHECK(cards::kreuz == json("kreuz")); - CHECK(cards::pik == json("pik")); - CHECK(cards::herz == json("herz")); - CHECK(cards::karo == json("karo")); - - // invalid json -> first enum - CHECK(cards::kreuz == json("what?").get()); - } - - SECTION("traditional enum") - { - // enum -> json - CHECK(json(TS_STOPPED) == "stopped"); - CHECK(json(TS_RUNNING) == "running"); - CHECK(json(TS_COMPLETED) == "completed"); - CHECK(json(TS_INVALID) == json()); - - // json -> enum - CHECK(TS_STOPPED == json("stopped")); - CHECK(TS_RUNNING == json("running")); - CHECK(TS_COMPLETED == json("completed")); - CHECK(TS_INVALID == json()); - - // invalid json -> first enum - CHECK(TS_INVALID == json("what?").get()); - } -} - -enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping - -// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards, -{ - {strict_cards::kreuz, "kreuz"}, - {strict_cards::pik, "pik"}, - {strict_cards::pik, "puk"}, // second entry for cards::pik; will not be used - {strict_cards::herz, "herz"}, - {strict_cards::karo, "karo"} -}) - -enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) -{ - STRICT_TS_STOPPED, - STRICT_TS_RUNNING, - STRICT_TS_COMPLETED, - STRICT_TS_OTHER, // STRICT_TS_OTHER not in mapping - STRICT_TS_INVALID = -1, -}; - -// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState, -{ - {STRICT_TS_INVALID, nullptr}, - {STRICT_TS_STOPPED, "stopped"}, - {STRICT_TS_RUNNING, "running"}, - {STRICT_TS_COMPLETED, "completed"}, -}) - -// regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on -// unqualified lookup of a helper name that a user's own namespace may also declare -namespace ns_with_colliding_name -{ -// NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name -inline void templated_json_throw(int /*unused*/) {} - -enum class colliding_enum { a, b }; - -// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive -NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(colliding_enum, -{ - {colliding_enum::a, "a"}, - {colliding_enum::b, "b"} -}) -} // namespace ns_with_colliding_name - -TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name") -{ - using ns_with_colliding_name::colliding_enum; - - CHECK(json(colliding_enum::a) == "a"); - CHECK(colliding_enum::b == json("b")); - - json _; - CHECK_THROWS_WITH_AS(_ = json("nope").get(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&); -} - -TEST_CASE("Strict JSON to enum mapping") -{ - SECTION("enum class") - { - // enum -> json - CHECK(json(strict_cards::kreuz) == "kreuz"); - CHECK(json(strict_cards::pik) == "pik"); - CHECK(json(strict_cards::herz) == "herz"); - CHECK(json(strict_cards::karo) == "karo"); - - // json -> enum - CHECK(json("kreuz").get() == strict_cards::kreuz); - CHECK(json("pik").get() == strict_cards::pik); - CHECK(json("herz").get() == strict_cards::herz); - CHECK(json("karo").get() == strict_cards::karo); - - // comparison of enum and json - CHECK(strict_cards::kreuz == json("kreuz")); - CHECK(strict_cards::pik == json("pik")); - CHECK(strict_cards::herz == json("herz")); - CHECK(strict_cards::karo == json("karo")); - - // invalid json -> exception thrown - json _; - CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); - - // conversion of unmapped enum -> exception thrown - CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); - - // comparing an unmapped enum with json throws the same exception - // (the scalar comparison operators used to be noexcept, so this - // called std::terminate) - CHECK_THROWS_WITH_AS(static_cast(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); - CHECK_THROWS_WITH_AS(static_cast(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); - - // invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the - // message (regression test for #5667); such strings can reach get() unvalidated, - // e.g. from from_cbor()/from_msgpack() (#5529) - const json j_invalid_utf8 = "\xFF"; - CHECK_THROWS_WITH_AS(_ = j_invalid_utf8.get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"\xEF\xBF\xBD\"", json::out_of_range&); - } - - SECTION("traditional enum") - { - // enum -> json - CHECK(json(STRICT_TS_STOPPED) == "stopped"); - CHECK(json(STRICT_TS_RUNNING) == "running"); - CHECK(json(STRICT_TS_COMPLETED) == "completed"); - CHECK(json(STRICT_TS_INVALID) == json()); - - // json -> enum - CHECK(json("stopped").get() == STRICT_TS_STOPPED); - CHECK(json("running").get() == STRICT_TS_RUNNING); - CHECK(json("completed").get() == STRICT_TS_COMPLETED); - CHECK(json().get() == STRICT_TS_INVALID); - - // comparison of enum and json - CHECK(STRICT_TS_STOPPED == json("stopped")); - CHECK(STRICT_TS_RUNNING == json("running")); - CHECK(STRICT_TS_COMPLETED == json("completed")); - CHECK(STRICT_TS_INVALID == json()); - - // invalid json -> exception thrown - json _; - CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&); - - // conversion of unmapped enum -> exception thrown - CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); - - // comparing an unmapped enum with json throws the same exception - CHECK_THROWS_WITH_AS(static_cast(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); - } -} - - -#ifdef JSON_HAS_CPP_17 -#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM -TEST_CASE("std::filesystem::path") -{ - SECTION("ascii") - { - json const j_string = "Path"; - auto p = j_string.template get(); - json const j_path = p; - - CHECK(j_path.template get() == - j_string.template get()); - } - - SECTION("utf-8") - { - json const j_string = "P\xc4\x9b\xc5\xa1ina"; - auto p = j_string.template get(); - json const j_path = p; - - CHECK(j_path.template get() == - j_string.template get()); - } -} -#endif - -// the ADL to_json overload for std::u8string only exists under the same guard -// as std::filesystem::path support (it is otherwise only reached indirectly, -// via std::filesystem::path::u8string()) -- mirror both #if conditions from -// include/nlohmann/detail/conversions/to_json.hpp exactly -#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM -#if defined(__cpp_lib_char8_t) -TEST_CASE("std::u8string") -{ - SECTION("ascii") - { - const std::u8string s = u8"Path"; - json const j = s; - - CHECK(j.template get() == "Path"); - } - - SECTION("utf-8") - { - // use \u universal-character-names (rather than raw \x byte escapes - // or literal non-ASCII source bytes) to compose the multi-byte UTF-8 - // encoding -- MSVC treats \x escapes used that way inside a u8 - // literal as a nonstandard extension (warning C5321), which some of - // our CI configs promote to an error; \u is portable and produces - // the exact same encoded bytes without depending on the source - // file's encoding - const std::u8string s = u8"P\u011B\u0161ina"; - json const j = s; - - CHECK(j.template get() == "P\xc4\x9b\xc5\xa1ina"); - } -} -#endif -#endif - -#if !defined(JSON_NOEXCEPTION) -namespace -{ -// a type whose to_json reports an error by throwing, used below to check that -// converting a std::optional to JSON propagates an exception thrown while -// converting its contained value instead of calling std::terminate (#5642) -struct throwing_to_json_type {}; - -[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/) -{ - throw std::runtime_error("cannot serialize throwing_to_json_type"); -} -} // namespace -#endif - -TEST_CASE("std::optional") -{ - SECTION("null") - { - const json j_null; - const std::optional opt_null; - - CHECK(json(opt_null) == j_null); - CHECK(j_null.get>() == std::nullopt); - - // Constructing std::optional directly from JSON null throws because - // std::optional's own converting constructor is chosen over basic_json's - // operator T(). This is a language-level limitation (std::optional is - // constructible from T, and T is constructible from basic_json via the - // operator); there is no SFINAE path that distinguishes "call from inside - // std::optional's constructor" from "direct call". Use get>() - // or get_to() instead for correct null handling. See #4864 and #5246. - CHECK_THROWS_WITH_AS(std::optional(j_null), - "[json.exception.type_error.302] type must be string, but is null", json::type_error&); - CHECK_THROWS_WITH_AS(std::optional(j_null), - "[json.exception.type_error.302] type must be number, but is null", json::type_error&); - - // Assignment goes through the same overload resolution as direct - // construction, so it throws for the same reason. This relies on - // basic_json's implicit conversion operator, so it only applies - // when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default). -#if JSON_USE_IMPLICIT_CONVERSIONS - std::optional opt_assign; - CHECK_THROWS_WITH_AS(opt_assign = j_null, - "[json.exception.type_error.302] type must be string, but is null", json::type_error&); -#endif - - // get_to() is the correct way to obtain std::nullopt from a JSON null. - std::optional opt_get_to = "placeholder"; - j_null.get_to(opt_get_to); - CHECK(opt_get_to == std::nullopt); - } - - SECTION("string") - { - json j_string = "string"; - std::optional opt_string = "string"; - - CHECK(json(opt_string) == j_string); - CHECK(std::optional(j_string) == opt_string); - // false positive: Infer attributes the destruction of the temporaries above to opt_string - // @infer-ignore USE_AFTER_DELETE - } - - SECTION("bool") - { - json j_bool = true; - std::optional opt_bool = true; - - CHECK(json(opt_bool) == j_bool); - CHECK(std::optional(j_bool) == opt_bool); - } - - SECTION("number") - { - json j_number = 1; - std::optional opt_int = 1; - - CHECK(json(opt_int) == j_number); - CHECK(j_number.get>() == opt_int); - } - - SECTION("array") - { - json j_array = {1, 2, nullptr}; - std::vector> opt_array = {{1, 2, std::nullopt}}; - - CHECK(json(opt_array) == j_array); - CHECK(j_array.get>>() == opt_array); - } - - SECTION("object") - { - json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}}; - std::map> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; - - CHECK(json(opt_object) == j_object); - CHECK(std::map>(j_object) == opt_object); - } - -#if !defined(JSON_NOEXCEPTION) - SECTION("exception from contained value's to_json propagates (#5642)") - { - // to_json(BasicJsonType&, const std::optional&) must not be - // noexcept: it calls T's to_json, which may throw (a user-defined - // to_json that reports an error, or std::bad_alloc for T = - // std::string/vector/json). Before the fix, this called - // std::terminate() instead of letting the exception propagate. - const std::optional opt = throwing_to_json_type{}; - CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); - - // the conversion is noexcept exactly when converting the contained value is - static_assert(!std::is_nothrow_constructible&>::value); - static_assert(std::is_nothrow_constructible&>::value); - } -#endif -} -#endif - #ifdef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_17 #endif diff --git a/tests/src/unit-conversions2.cpp b/tests/src/unit-conversions2.cpp new file mode 100644 index 000000000..eca482b83 --- /dev/null +++ b/tests/src/unit-conversions2.cpp @@ -0,0 +1,868 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// 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_ 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_ (do not remove; see note at top of file) + +#include "doctest_compatibility.h" + +// skip tests if JSON_DisableEnumSerialization=ON (#4384) +#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1) + #define SKIP_TESTS_FOR_ENUM_SERIALIZATION +#endif + +#define JSON_TESTS_PRIVATE +#include +using nlohmann::json; + +#include +#include +#include +#include +#include +#include +#include + +// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") + +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +#ifdef JSON_HAS_CPP_17 + #if __has_include() + #include + #elif __has_include() + #include + #endif +#endif + +#if defined(JSON_HAS_CPP_17) + #include +#endif + +TEST_CASE("value conversion") +{ + SECTION("get a binary value (explicit)") + { + json::binary_t const n_reference{{1, 2, 3}}; + json j(n_reference); + + SECTION("binary_t") + { + json::binary_t const b = j.get(); + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + + SECTION("get_binary()") + { + SECTION("non-const") + { + auto& b = j.get_binary(); + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + + SECTION("non-const") + { + const json j_const = j; // NOLINT(performance-unnecessary-copy-initialization) + const auto& b = j_const.get_binary(); + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + } + + SECTION("exception in case of a non-string type") + { + json j_null(json::value_t::null); + json j_object(json::value_t::object); + json j_array(json::value_t::array); + json j_string(json::value_t::string); + json j_boolean(json::value_t::boolean); + const json j_null_const(json::value_t::null); + const json j_object_const(json::value_t::object); + const json j_array_const(json::value_t::array); + const json j_string_const(json::value_t::string); + const json j_boolean_const(json::value_t::boolean); + + CHECK_THROWS_WITH_AS(j_null.get(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object.get(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array.get(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string.get(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean.get(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + + CHECK_THROWS_WITH_AS(j_null_const.get(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object_const.get(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array_const.get(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string_const.get(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean_const.get(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + + CHECK_THROWS_WITH_AS(j_null.get_binary(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object.get_binary(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array.get_binary(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string.get_binary(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean.get_binary(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + + CHECK_THROWS_WITH_AS(j_null_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is null", + json::type_error&); + CHECK_THROWS_WITH_AS(j_object_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is object", + json::type_error&); + CHECK_THROWS_WITH_AS(j_array_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is array", + json::type_error&); + CHECK_THROWS_WITH_AS(j_string_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is string", + json::type_error&); + CHECK_THROWS_WITH_AS(j_boolean_const.get_binary(), + "[json.exception.type_error.302] type must be binary, but is boolean", + json::type_error&); + } + } + +#if JSON_USE_IMPLICIT_CONVERSIONS + SECTION("get a binary value (implicit)") + { + json::binary_t const n_reference{{1, 2, 3}}; + json const j(n_reference); + + SECTION("binary_t") + { + json::binary_t const b = j; + CHECK(*json(b).m_data.m_value.binary == *j.m_data.m_value.binary); + } + } +#endif + +#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION + SECTION("get an enum") + { + enum c_enum { value_1, value_2 }; // NOLINT(cppcoreguidelines-use-enum-class) + enum class cpp_enum { value_1, value_2 }; + + CHECK(json(value_1).get() == value_1); + CHECK(json(cpp_enum::value_1).get() == cpp_enum::value_1); + } + + SECTION("get an enum with underlying type bool (#5671)") + { + enum class bool_enum : bool { off, on }; + + CHECK(json(bool_enum::off).get() == bool_enum::off); + CHECK(json(bool_enum::on).get() == bool_enum::on); + } +#endif + + SECTION("more involved conversions") + { + SECTION("object-like STL containers") + { + json const j1 = {{"one", 1}, {"two", 2}, {"three", 3}}; + json const j2 = {{"one", 1u}, {"two", 2u}, {"three", 3u}}; + json const j3 = {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}; + json const j4 = {{"one", true}, {"two", false}, {"three", true}}; + json const j5 = {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}; + + SECTION("std::map") + { + CHECK(j1.get>() == (std::map {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::map {{"one", true}, {"two", false}, {"three", true}})); + CHECK(j5.get>() == (std::map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + } + + SECTION("std::unordered_map") + { + CHECK(j1.get>() == (std::unordered_map {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::unordered_map {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::unordered_map {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::unordered_map {{"one", true}, {"two", false}, {"three", true}})); + const auto m5 = j5.get>(); + CHECK(m5 == (std::unordered_map {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + CHECK(m5.at("one") == "eins"); + } + + SECTION("reserve is called on containers that support it (#5406)") + { + // build a larger object so that a missing/incorrect reserve() + // call would be more likely to corrupt or drop elements + json j_large; + for (int i = 0; i < 100; ++i) + { + j_large[std::to_string(i)] = i; + } + + SECTION("std::unordered_map (supports reserve)") + { + const auto m = j_large.get>(); + CHECK(m.size() == 100); + for (int i = 0; i < 100; ++i) + { + CHECK(m.at(std::to_string(i)) == i); + } + } + + SECTION("std::map (no reserve, fallback path)") + { + const auto m = j_large.get>(); + CHECK(m.size() == 100); + for (int i = 0; i < 100; ++i) + { + CHECK(m.at(std::to_string(i)) == i); + } + } + } + + SECTION("std::multimap") + { + CHECK(j1.get>() == (std::multimap {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::multimap {{"one", true}, {"two", false}, {"three", true}})); + const auto m5 = j5.get>(); + CHECK(m5 == (std::multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + CHECK(m5.find("one")->second == "eins"); + } + + SECTION("std::unordered_multimap") + { + CHECK(j1.get>() == (std::unordered_multimap {{"one", 1}, {"two", 2}, {"three", 3}})); + CHECK(j2.get>() == (std::unordered_multimap {{"one", 1u}, {"two", 2u}, {"three", 3u}})); + CHECK(j3.get>() == (std::unordered_multimap {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}})); + CHECK(j4.get>() == (std::unordered_multimap {{"one", true}, {"two", false}, {"three", true}})); + const auto m5 = j5.get>(); + CHECK(m5 == (std::unordered_multimap {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}})); + CHECK(m5.find("one")->second == "eins"); + } + + SECTION("exception in case of a non-object type") + { + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be object, but is null", json::type_error&); + } + } + + SECTION("array-like STL containers") + { + json const j1 = {1, 2, 3, 4}; + json const j2 = {1u, 2u, 3u, 4u}; + json const j3 = {1.2, 2.3, 3.4, 4.5}; + json const j4 = {true, false, true}; + json const j5 = {"one", "two", "three"}; + + SECTION("std::list") + { + CHECK(j1.get>() == (std::list {1, 2, 3, 4})); + CHECK(j2.get>() == (std::list {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::list {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::list {true, false, true})); + CHECK(j5.get>() == (std::list {"one", "two", "three"})); + } + + SECTION("std::forward_list") + { + CHECK(j1.get>() == (std::forward_list {1, 2, 3, 4})); + CHECK(j2.get>() == (std::forward_list {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::forward_list {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::forward_list {true, false, true})); + CHECK(j5.get>() == (std::forward_list {"one", "two", "three"})); + } + + SECTION("std::array") + { + CHECK(j1.get>() == (std::array {{1, 2, 3, 4}})); + // only the first 3 elements of j2 are converted, since the target array is smaller + CHECK(j2.get>() == (std::array {{1u, 2u, 3u}})); + CHECK(j3.get>() == (std::array {{1.2, 2.3, 3.4, 4.5}})); + CHECK(j4.get>() == (std::array {{true, false, true}})); + CHECK(j5.get>() == (std::array {{"one", "two", "three"}})); + + SECTION("std::array is larger than JSON") + { + std::array arr6 = {{1, 2, 3, 4, 5, 6}}; + CHECK_THROWS_WITH_AS(j1.get_to(arr6), "[json.exception.out_of_range.401] " + "array index 4 is out of range", json::out_of_range&); + } + + SECTION("std::array is smaller than JSON") + { + std::array arr2 = {{8, 9}}; + j1.get_to(arr2); + CHECK(arr2[0] == 1); + CHECK(arr2[1] == 2); + } + } + + SECTION("std::valarray") + { + // valarray has no operator== that returns bool, so compare via a vector copy + const auto v1 = j1.get>(); + CHECK((std::vector(std::begin(v1), std::end(v1)) == std::vector {1, 2, 3, 4})); + const auto v2 = j2.get>(); + CHECK((std::vector(std::begin(v2), std::end(v2)) == std::vector {1u, 2u, 3u, 4u})); + const auto v3 = j3.get>(); + CHECK((std::vector(std::begin(v3), std::end(v3)) == std::vector {1.2, 2.3, 3.4, 4.5})); + const auto v4 = j4.get>(); + CHECK((std::vector(std::begin(v4), std::end(v4)) == std::vector {true, false, true})); + const auto v5 = j5.get>(); + CHECK((std::vector(std::begin(v5), std::end(v5)) == std::vector {"one", "two", "three"})); + } + + SECTION("std::vector") + { + CHECK(j1.get>() == (std::vector {1, 2, 3, 4})); + CHECK(j2.get>() == (std::vector {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::vector {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::vector {true, false, true})); + CHECK(j5.get>() == (std::vector {"one", "two", "three"})); + } + + SECTION("std::deque") + { + CHECK(j1.get>() == (std::deque {1, 2, 3, 4})); + CHECK(j2.get>() == (std::deque {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::deque {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::deque {true, false, true})); + CHECK(j5.get>() == (std::deque {"one", "two", "three"})); + } + + SECTION("std::set") + { + CHECK(j1.get>() == (std::set {1, 2, 3, 4})); + CHECK(j2.get>() == (std::set {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::set {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::set {true, false, true})); + CHECK(j5.get>() == (std::set {"one", "two", "three"})); + } + + SECTION("std::unordered_set") + { + CHECK(j1.get>() == (std::unordered_set {1, 2, 3, 4})); + CHECK(j2.get>() == (std::unordered_set {1u, 2u, 3u, 4u})); + CHECK(j3.get>() == (std::unordered_set {1.2, 2.3, 3.4, 4.5})); + CHECK(j4.get>() == (std::unordered_set {true, false, true})); + CHECK(j5.get>() == (std::unordered_set {"one", "two", "three"})); + } + + SECTION("std::map (array of pairs)") + { + const std::map m{{0, 1}, {1, 2}, {2, 3}}; + json const j6 = m; + + auto m2 = j6.get>(); + CHECK(m == m2); + + json const j7 = {0, 1, 2, 3}; + json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); +#endif + CHECK_THROWS_WITH_AS((j8.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); + + SECTION("superfluous entries") + { + json const j9 = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; + m2 = j9.get>(); + CHECK(m == m2); + } + } + + SECTION("std::unordered_map (array of pairs)") + { + const std::unordered_map m{{0, 1}, {1, 2}, {2, 3}}; + json const j6 = m; + + auto m2 = j6.get>(); + CHECK(m == m2); + + json const j7 = {0, 1, 2, 3}; + json const j8 = 2; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] (/0) type must be array, " + "but is number", json::type_error&); +#else + CHECK_THROWS_WITH_AS((j7.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); +#endif + CHECK_THROWS_WITH_AS((j8.get>()), + "[json.exception.type_error.302] type must be array, " + "but is number", json::type_error&); + + SECTION("superfluous entries") + { + json const j9{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; + m2 = j9.get>(); + CHECK(m == m2); + } + } + + SECTION("exception in case of a non-object type") + { + // does type really must be an array? or it rather must not be null? + // that's what I thought when other test like this one broke + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + CHECK_THROWS_WITH_AS( + (json().get>()), + "[json.exception.type_error.302] type must be array, but is null", json::type_error&); + } + } + } +} + +enum class cards {kreuz, pik, herz, karo}; + +// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM(cards, +{ + {cards::kreuz, "kreuz"}, + {cards::pik, "pik"}, + {cards::pik, "puk"}, // second entry for cards::puk; will not be used + {cards::herz, "herz"}, + {cards::karo, "karo"} +}) + +enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) +{ + TS_STOPPED, + TS_RUNNING, + TS_COMPLETED, + TS_INVALID = -1, +}; + +// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, +{ + {TS_INVALID, nullptr}, + {TS_STOPPED, "stopped"}, + {TS_RUNNING, "running"}, + {TS_COMPLETED, "completed"}, +}) + +TEST_CASE("JSON to enum mapping") +{ + SECTION("enum class") + { + // enum -> json + CHECK(json(cards::kreuz) == "kreuz"); + CHECK(json(cards::pik) == "pik"); + CHECK(json(cards::herz) == "herz"); + CHECK(json(cards::karo) == "karo"); + + // json -> enum + CHECK(cards::kreuz == json("kreuz")); + CHECK(cards::pik == json("pik")); + CHECK(cards::herz == json("herz")); + CHECK(cards::karo == json("karo")); + + // invalid json -> first enum + CHECK(cards::kreuz == json("what?").get()); + } + + SECTION("traditional enum") + { + // enum -> json + CHECK(json(TS_STOPPED) == "stopped"); + CHECK(json(TS_RUNNING) == "running"); + CHECK(json(TS_COMPLETED) == "completed"); + CHECK(json(TS_INVALID) == json()); + + // json -> enum + CHECK(TS_STOPPED == json("stopped")); + CHECK(TS_RUNNING == json("running")); + CHECK(TS_COMPLETED == json("completed")); + CHECK(TS_INVALID == json()); + + // invalid json -> first enum + CHECK(TS_INVALID == json("what?").get()); + } +} + +enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping + +// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards, +{ + {strict_cards::kreuz, "kreuz"}, + {strict_cards::pik, "pik"}, + {strict_cards::pik, "puk"}, // second entry for cards::pik; will not be used + {strict_cards::herz, "herz"}, + {strict_cards::karo, "karo"} +}) + +enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class) +{ + STRICT_TS_STOPPED, + STRICT_TS_RUNNING, + STRICT_TS_COMPLETED, + STRICT_TS_OTHER, // STRICT_TS_OTHER not in mapping + STRICT_TS_INVALID = -1, +}; + +// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState, +{ + {STRICT_TS_INVALID, nullptr}, + {STRICT_TS_STOPPED, "stopped"}, + {STRICT_TS_RUNNING, "running"}, + {STRICT_TS_COMPLETED, "completed"}, +}) + +// regression test for #5708 item 2: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT must not rely on +// unqualified lookup of a helper name that a user's own namespace may also declare +namespace ns_with_colliding_name +{ +// NOLINTNEXTLINE(misc-use-internal-linkage) - used to shadow the library's internal helper name +inline void templated_json_throw(int /*unused*/) {} + +enum class colliding_enum { a, b }; + +// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(colliding_enum, +{ + {colliding_enum::a, "a"}, + {colliding_enum::b, "b"} +}) +} // namespace ns_with_colliding_name + +TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT in a namespace with a colliding name") +{ + using ns_with_colliding_name::colliding_enum; + + CHECK(json(colliding_enum::a) == "a"); + CHECK(colliding_enum::b == json("b")); + + json _; + CHECK_THROWS_WITH_AS(_ = json("nope").get(), "[json.exception.out_of_range.410] enum value out of range for colliding_enum: \"nope\"", json::out_of_range&); +} + +TEST_CASE("Strict JSON to enum mapping") +{ + SECTION("enum class") + { + // enum -> json + CHECK(json(strict_cards::kreuz) == "kreuz"); + CHECK(json(strict_cards::pik) == "pik"); + CHECK(json(strict_cards::herz) == "herz"); + CHECK(json(strict_cards::karo) == "karo"); + + // json -> enum + CHECK(json("kreuz").get() == strict_cards::kreuz); + CHECK(json("pik").get() == strict_cards::pik); + CHECK(json("herz").get() == strict_cards::herz); + CHECK(json("karo").get() == strict_cards::karo); + + // comparison of enum and json + CHECK(strict_cards::kreuz == json("kreuz")); + CHECK(strict_cards::pik == json("pik")); + CHECK(strict_cards::herz == json("herz")); + CHECK(strict_cards::karo == json("karo")); + + // invalid json -> exception thrown + json _; + CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&); + + // conversion of unmapped enum -> exception thrown + CHECK_THROWS_WITH_AS(json(strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + + // comparing an unmapped enum with json throws the same exception + // (the scalar comparison operators used to be noexcept, so this + // called std::terminate) + CHECK_THROWS_WITH_AS(static_cast(strict_cards::andere == json("andere")), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + CHECK_THROWS_WITH_AS(static_cast(json("andere") != strict_cards::andere), "[json.exception.out_of_range.410] enum value out of range for strict_cards", json::out_of_range&); + + // invalid UTF-8 -> out_of_range.410, not the type_error.316 thrown while building the + // message (regression test for #5667); such strings can reach get() unvalidated, + // e.g. from from_cbor()/from_msgpack() (#5529) + const json j_invalid_utf8 = "\xFF"; + CHECK_THROWS_WITH_AS(_ = j_invalid_utf8.get(), "[json.exception.out_of_range.410] enum value out of range for strict_cards: \"\xEF\xBF\xBD\"", json::out_of_range&); + } + + SECTION("traditional enum") + { + // enum -> json + CHECK(json(STRICT_TS_STOPPED) == "stopped"); + CHECK(json(STRICT_TS_RUNNING) == "running"); + CHECK(json(STRICT_TS_COMPLETED) == "completed"); + CHECK(json(STRICT_TS_INVALID) == json()); + + // json -> enum + CHECK(json("stopped").get() == STRICT_TS_STOPPED); + CHECK(json("running").get() == STRICT_TS_RUNNING); + CHECK(json("completed").get() == STRICT_TS_COMPLETED); + CHECK(json().get() == STRICT_TS_INVALID); + + // comparison of enum and json + CHECK(STRICT_TS_STOPPED == json("stopped")); + CHECK(STRICT_TS_RUNNING == json("running")); + CHECK(STRICT_TS_COMPLETED == json("completed")); + CHECK(STRICT_TS_INVALID == json()); + + // invalid json -> exception thrown + json _; + CHECK_THROWS_WITH_AS(_ = json("what?").get(), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState: \"what?\"", json::out_of_range&); + + // conversion of unmapped enum -> exception thrown + CHECK_THROWS_WITH_AS(json(STRICT_TS_OTHER), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); + + // comparing an unmapped enum with json throws the same exception + CHECK_THROWS_WITH_AS(static_cast(STRICT_TS_OTHER < json("x")), "[json.exception.out_of_range.410] enum value out of range for StrictTaskState", json::out_of_range&); + } +} + + +#ifdef JSON_HAS_CPP_17 +#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM +TEST_CASE("std::filesystem::path") +{ + SECTION("ascii") + { + json const j_string = "Path"; + auto p = j_string.template get(); + json const j_path = p; + + CHECK(j_path.template get() == + j_string.template get()); + } + + SECTION("utf-8") + { + json const j_string = "P\xc4\x9b\xc5\xa1ina"; + auto p = j_string.template get(); + json const j_path = p; + + CHECK(j_path.template get() == + j_string.template get()); + } +} +#endif + +// the ADL to_json overload for std::u8string only exists under the same guard +// as std::filesystem::path support (it is otherwise only reached indirectly, +// via std::filesystem::path::u8string()) -- mirror both #if conditions from +// include/nlohmann/detail/conversions/to_json.hpp exactly +#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM +#if defined(__cpp_lib_char8_t) +TEST_CASE("std::u8string") +{ + SECTION("ascii") + { + const std::u8string s = u8"Path"; + json const j = s; + + CHECK(j.template get() == "Path"); + } + + SECTION("utf-8") + { + // use \u universal-character-names (rather than raw \x byte escapes + // or literal non-ASCII source bytes) to compose the multi-byte UTF-8 + // encoding -- MSVC treats \x escapes used that way inside a u8 + // literal as a nonstandard extension (warning C5321), which some of + // our CI configs promote to an error; \u is portable and produces + // the exact same encoded bytes without depending on the source + // file's encoding + const std::u8string s = u8"P\u011B\u0161ina"; + json const j = s; + + CHECK(j.template get() == "P\xc4\x9b\xc5\xa1ina"); + } +} +#endif +#endif + +#if !defined(JSON_NOEXCEPTION) +namespace +{ +// a type whose to_json reports an error by throwing, used below to check that +// converting a std::optional to JSON propagates an exception thrown while +// converting its contained value instead of calling std::terminate (#5642) +struct throwing_to_json_type {}; + +[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/) +{ + throw std::runtime_error("cannot serialize throwing_to_json_type"); +} +} // namespace +#endif + +TEST_CASE("std::optional") +{ + SECTION("null") + { + const json j_null; + const std::optional opt_null; + + CHECK(json(opt_null) == j_null); + CHECK(j_null.get>() == std::nullopt); + + // Constructing std::optional directly from JSON null throws because + // std::optional's own converting constructor is chosen over basic_json's + // operator T(). This is a language-level limitation (std::optional is + // constructible from T, and T is constructible from basic_json via the + // operator); there is no SFINAE path that distinguishes "call from inside + // std::optional's constructor" from "direct call". Use get>() + // or get_to() instead for correct null handling. See #4864 and #5246. + CHECK_THROWS_WITH_AS(std::optional(j_null), + "[json.exception.type_error.302] type must be string, but is null", json::type_error&); + CHECK_THROWS_WITH_AS(std::optional(j_null), + "[json.exception.type_error.302] type must be number, but is null", json::type_error&); + + // Assignment goes through the same overload resolution as direct + // construction, so it throws for the same reason. This relies on + // basic_json's implicit conversion operator, so it only applies + // when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default). +#if JSON_USE_IMPLICIT_CONVERSIONS + std::optional opt_assign; + CHECK_THROWS_WITH_AS(opt_assign = j_null, + "[json.exception.type_error.302] type must be string, but is null", json::type_error&); +#endif + + // get_to() is the correct way to obtain std::nullopt from a JSON null. + std::optional opt_get_to = "placeholder"; + j_null.get_to(opt_get_to); + CHECK(opt_get_to == std::nullopt); + } + + SECTION("string") + { + json j_string = "string"; + std::optional opt_string = "string"; + + CHECK(json(opt_string) == j_string); + CHECK(std::optional(j_string) == opt_string); + // false positive: Infer attributes the destruction of the temporaries above to opt_string + // @infer-ignore USE_AFTER_DELETE + } + + SECTION("bool") + { + json j_bool = true; + std::optional opt_bool = true; + + CHECK(json(opt_bool) == j_bool); + CHECK(std::optional(j_bool) == opt_bool); + } + + SECTION("number") + { + json j_number = 1; + std::optional opt_int = 1; + + CHECK(json(opt_int) == j_number); + CHECK(j_number.get>() == opt_int); + } + + SECTION("array") + { + json j_array = {1, 2, nullptr}; + std::vector> opt_array = {{1, 2, std::nullopt}}; + + CHECK(json(opt_array) == j_array); + CHECK(j_array.get>>() == opt_array); + } + + SECTION("object") + { + json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}}; + std::map> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}}; + + CHECK(json(opt_object) == j_object); + CHECK(std::map>(j_object) == opt_object); + } + +#if !defined(JSON_NOEXCEPTION) + SECTION("exception from contained value's to_json propagates (#5642)") + { + // to_json(BasicJsonType&, const std::optional&) must not be + // noexcept: it calls T's to_json, which may throw (a user-defined + // to_json that reports an error, or std::bad_alloc for T = + // std::string/vector/json). Before the fix, this called + // std::terminate() instead of letting the exception propagate. + const std::optional opt = throwing_to_json_type{}; + CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); + + // the conversion is noexcept exactly when converting the contained value is + static_assert(!std::is_nothrow_constructible&>::value); + static_assert(std::is_nothrow_constructible&>::value); + } +#endif +} +#endif + +#ifdef JSON_HAS_CPP_17 + #undef JSON_HAS_CPP_17 +#endif + +#ifdef JSON_HAS_CPP_14 + #undef JSON_HAS_CPP_14 +#endif +DOCTEST_CLANG_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-custom-base-class.cpp b/tests/src/unit-custom-base-class.cpp index 38b665793..b9e544705 100644 --- a/tests/src/unit-custom-base-class.cpp +++ b/tests/src/unit-custom-base-class.cpp @@ -505,7 +505,7 @@ static json_with_const_base make_nested_array(std::size_t depth) { if (depth == 0) { - return json_with_const_base(1); + return json_with_const_base(1); // NOLINT(modernize-return-braced-init-list): {1} would be an array } return json_with_const_base::array({make_nested_array(depth - 1)}); } diff --git a/tests/src/unit-disabled_exceptions.cpp b/tests/src/unit-disabled_exceptions.cpp index 8e3adf944..40a37c339 100644 --- a/tests/src/unit-disabled_exceptions.cpp +++ b/tests/src/unit-disabled_exceptions.cpp @@ -14,6 +14,9 @@ DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") #include using json = nlohmann::json; +#ifdef JSON_TEST_NO_GLOBAL_UDLS + using namespace nlohmann::literals; // NOLINT(google-build-using-namespace) +#endif ///////////////////////////////////////////////////////////////////// // for #2824 diff --git a/tests/src/unit-element_access2.cpp b/tests/src/unit-element_access2.cpp index efd7b15a0..9a7a58e6b 100644 --- a/tests/src/unit-element_access2.cpp +++ b/tests/src/unit-element_access2.cpp @@ -1963,8 +1963,8 @@ TEST_CASE("operator[] with user-defined std::string_view-convertible types") }; json j = {{"foo", "from_class"}, {"bar", "from_struct"}}; - TestClass foo_obj; - TestStruct bar_obj; + const TestClass foo_obj; + const TestStruct bar_obj; SECTION("read access") { @@ -2005,6 +2005,10 @@ TEST_CASE("keys convertible to std::string_view work with all lookup functions ( // 3.12.0, such a key worked with at, the const operator[], find, count and // contains via the conversion to std::string; #4958 made the KeyType&& // templates win overload resolution for it instead, and those then failed + // the lookups pick the conversion to std::string_view, which leaves the one + // to std::string unused; it has to exist to reproduce the ambiguity + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") struct DualKey { operator std::string() const @@ -2016,6 +2020,7 @@ TEST_CASE("keys convertible to std::string_view work with all lookup functions ( return "a"; } }; + DOCTEST_CLANG_SUPPRESS_WARNING_POP SECTION("nlohmann::json") { diff --git a/tests/src/unit-modifiers.cpp b/tests/src/unit-modifiers.cpp index 532283f95..3377eace0 100644 --- a/tests/src/unit-modifiers.cpp +++ b/tests/src/unit-modifiers.cpp @@ -1162,6 +1162,7 @@ TEST_CASE("update() on deeply nested values") TEST_CASE("update() with an argument that aliases *this (#5641)") { +#if !defined(JSON_NOEXCEPTION) // checks which exception is thrown, and that nothing changed SECTION("the target is checked before the argument, as before the copy") { json j = 1; @@ -1172,6 +1173,7 @@ TEST_CASE("update() with an argument that aliases *this (#5641)") CHECK_THROWS_WITH_AS(k.update(json::array()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&); CHECK(k == json::object()); } +#endif SECTION("const reference") { diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index a92496144..c4b447d64 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -1560,7 +1560,7 @@ TEST_CASE("MessagePack") // dump() still requires valid UTF-8 and throws for such a value, // unless an error handler that replaces or ignores the bytes is // passed - CHECK_THROWS_AS(j_value.dump(), json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(j_value.dump()), json::type_error&); // the same bytes as an object key round-trip as well const std::vector ill_formed_key = {0x81, 0xa2, 0xc0, 0xae, 0x01}; diff --git a/tests/src/unit-ordered_map.cpp b/tests/src/unit-ordered_map.cpp index dce3f61a5..0d0f7730c 100644 --- a/tests/src/unit-ordered_map.cpp +++ b/tests/src/unit-ordered_map.cpp @@ -430,8 +430,8 @@ TEST_CASE("ordered_map") SECTION("with T& (lvalue)") { - std::string one = "1"; - std::string four = "four"; + std::string one = "1"; // NOLINT(misc-const-correctness): emplace must accept a non-const lvalue + std::string four = "four"; // NOLINT(misc-const-correctness): see above auto res1 = om.emplace("eins", one); CHECK(res1.first == om.begin()); @@ -467,7 +467,7 @@ TEST_CASE("ordered_map") SECTION("with key of key_type (non-template overload)") { const std::string key_vier{"vier"}; - std::string four = "four"; + std::string four = "four"; // NOLINT(misc-const-correctness): emplace must accept a non-const lvalue auto res4 = om.emplace(key_vier, four); CHECK(res4.first == om.begin() + 3); diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 450882a12..80ca214a4 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2520,7 +2520,7 @@ TEST_CASE("Universal Binary JSON Specification Examples 1") CHECK_NOTHROW(j = json::from_ubjson(v)); REQUIRE(j.is_string()); CHECK(j.get_ref() == std::string("\xc0\xae")); - CHECK_THROWS_AS(j.dump(), json::type_error&); + CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&); CHECK(json::from_ubjson(json::to_ubjson(j)) == j); // the same bytes as an object key round-trip as well diff --git a/tests/src/unit-wstring.cpp b/tests/src/unit-wstring.cpp index c71684002..c555d54da 100644 --- a/tests/src/unit-wstring.cpp +++ b/tests/src/unit-wstring.cpp @@ -37,10 +37,10 @@ TEST_CASE("wide strings") // 32-bit wchar_t first encodes it as an ill-formed three-byte // sequence (rejected one byte later, at column 3) const char* const error_low_surrogate = sizeof(wchar_t) == 2 - ? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'" + ? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'" : "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'"; const char* const error_high_surrogate = sizeof(wchar_t) == 2 - ? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'" + ? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'" : "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'"; // a lone low surrogate cannot start a pair