From a4556e826245dbc944883f5da45a735b7b37303f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:27:24 +0200 Subject: [PATCH 01/13] Fix CI: unused parse result and find() == npos in the Eisel-Lemire tests GCC (-Werror=unused-result) rejected CHECK_THROWS_WITH_AS(json::parse(...)) because parse() is [[nodiscard]]; assign the result to a dummy json as the other tests do. clang-tidy flagged longer.find('.') == npos with abseil-string-find-str-contains; store the position in a variable first. Signed-off-by: Niels Lohmann --- tests/src/unit-class_lexer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index 5647f4f46..b56e4bd0b 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp @@ -1286,7 +1286,8 @@ TEST_CASE("Eisel-Lemire float conversion") // than the distance to the rounding boundary, so it must not change std::string longer = token; const std::size_t e = longer.find('e'); - const std::string extra = longer.find('.') == std::string::npos ? ".000000000000000000001" : "000000000000000000001"; + const std::size_t dot = longer.find('.'); + const std::string extra = dot == std::string::npos ? ".000000000000000000001" : "000000000000000000001"; longer.insert(e == std::string::npos ? longer.size() : e, extra); CAPTURE(longer); if (eisel_lemire(longer, out)) @@ -1309,7 +1310,8 @@ TEST_CASE("Eisel-Lemire float conversion") CHECK(bits_of(json::parse("-65.613616999999977").get()) == bits_of(-65.613616999999977)); CHECK(bits_of(json::parse("2.2250738585072011e-308").get()) == 0x000FFFFFFFFFFFFFu); CHECK(bits_of(json::parse("4.9406564584124654e-324").get()) == 1u); - CHECK_THROWS_WITH_AS(json::parse("1.7976931348623159e308"), + json _; + CHECK_THROWS_WITH_AS(_ = json::parse("1.7976931348623159e308"), "[json.exception.out_of_range.406] number overflow parsing '1.7976931348623159e308'", json::out_of_range&); } } From 046ad7ebd8bfd7bdb666f8c0164ff15eda9f2900 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:29:25 +0200 Subject: [PATCH 02/13] Fix CI: useless casts to std::size_t in the string-scan tests GCC -Werror=useless-cast rejected static_cast(next() % n): on 64-bit Linux std::uint64_t and std::size_t are the same type, while the cast is needed where std::size_t is 32 bits wide. Draw the sizes from a 32-bit value instead, which converts to std::size_t implicitly on every platform. Signed-off-by: Niels Lohmann --- tests/src/unit-class_lexer.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index 30d8a5679..d8577f206 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp @@ -1378,16 +1378,23 @@ TEST_CASE("string scanning kernels") state ^= state << 17u; return state; }; + // the upper half as a 32-bit value: converts to std::size_t implicitly on + // every platform (a cast of std::uint64_t is useless where both are the + // same type, and required where std::size_t is 32 bits wide) + const auto next_small = [&next]() + { + return static_cast(next() >> 32u); + }; for (int round = 0; round < 100000; ++round) { // mostly ordinary text, so that runs span several words - std::string text(static_cast(next() % 8), '.'); - const auto count = static_cast(next() % 12); + std::string text(next_small() % 8u, '.'); + const std::size_t count = next_small() % 12u; for (std::size_t k = 0; k < count; ++k) { - const std::size_t p = (next() % 4 == 0) ? static_cast(next() % pieces.size()) : 0; + const std::size_t p = (next() % 4 == 0) ? next_small() % pieces.size() : 0; text += pieces[p]; - text += std::string(static_cast(next() % 10), 'x'); + text += std::string(next_small() % 10u, 'x'); } const auto* data = reinterpret_cast(text.data()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) for (std::size_t offset = 0; offset < 3 && offset <= text.size(); ++offset) From 87a041a9533707a85222f32918769c81a6f395d0 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:34:18 +0200 Subject: [PATCH 03/13] Fix CI: MSVC C4127 in the view builder and the single-header test build - msvc (Win32, /W4 /WX) reported C4127 (conditional expression is constant) for `TrailingCommas && cur() == ']'` and the like when the option is off. Route the template arguments through a static enabled() function, as json.hpp's nesting_depth_exhausted() does. - ci_test_single_header compiled unit-json_view_builder.cpp against single_include/, which does not contain the internal nlohmann/detail/view headers. Build that test only with the multiple headers. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/builder.hpp | 14 +++++++++++--- tests/CMakeLists.txt | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 45f547072..2e0151b31 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -418,6 +418,14 @@ class builder return w; } + /// a compile-time option as a runtime condition: testing the template + /// argument directly makes a condition like `TrailingCommas && c == ']'` + /// constant when the option is off, which MSVC reports as C4127 + static NLOHMANN_VIEW_ALWAYS_INLINE bool enabled(bool option) noexcept + { + return option; + } + /// The parse state and the parser proper. The cursor is a local object of /// run() whose address never escapes (everything it calls out of line is a /// member of the builder and gets the positions it needs), so that the @@ -547,7 +555,7 @@ arr_next: { return false; } - if (TrailingCommas && cur() == ']') + if (enabled(TrailingCommas) && cur() == ']') { ++p; goto close_container; @@ -614,7 +622,7 @@ obj_next: { return false; } - if (TrailingCommas && cur() == '}') + if (enabled(TrailingCommas) && cur() == '}') { ++p; goto close_container; @@ -756,7 +764,7 @@ indent_done: { ++p; } - if (Comments && cur() == '/') + if (enabled(Comments) && cur() == '/') { const unsigned char* const q = cold.comment(p); if (q == nullptr) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8c81b9123..71cfe2322 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -279,6 +279,10 @@ if(json_32bit_test_only) elseif(NOT json_32bit_test) list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp) endif() +if(NOT JSON_MultipleHeaders) + # the internal headers of json_view are not part of a single header yet + list(FILTER files EXCLUDE REGEX src/unit-json_view_builder.cpp) +endif() foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) From a8e9ce9417055eced5c94b9e0cdcd103d043c74f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:39:30 +0200 Subject: [PATCH 04/13] Fix CI: json_view tests without exceptions, on clang 3.6, and single header - ci_test_noexceptions: the helpers that compare the exceptions of json_document::parse() and json::parse() catch them outside a CHECK_THROWS, so with JSON_NOEXCEPTION the first parse error aborted the test. Compile those comparisons only with exceptions, as unit-class_parser.cpp does. - ci_test_gcc: -Werror=unused-result for CHECK_THROWS_AS(json_document:: parse(...)); assign the result to a dummy document. - ci_test_compilers_clang (3.6): `const json_view invalid;` needs a user-provided default constructor there (CWG 253); value-initialize it. - ci_test_single_header: json_view.hpp now exists as a single header and contains the internal view headers, so unit-json_view_builder.cpp includes it instead of the detail headers in that mode, and the test is built again with the single header. - Regenerate single_include/nlohmann/json_view.hpp for the builder change merged from json-view/08-view-builder. Signed-off-by: Niels Lohmann --- single_include/nlohmann/json_view.hpp | 14 +++++++++++--- tests/CMakeLists.txt | 4 ---- tests/src/unit-json_view.cpp | 15 ++++++++++++--- tests/src/unit-json_view_builder.cpp | 8 ++++++-- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index f97a01574..c0ba5f468 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -955,6 +955,14 @@ class builder return w; } + /// a compile-time option as a runtime condition: testing the template + /// argument directly makes a condition like `TrailingCommas && c == ']'` + /// constant when the option is off, which MSVC reports as C4127 + static NLOHMANN_VIEW_ALWAYS_INLINE bool enabled(bool option) noexcept + { + return option; + } + /// The parse state and the parser proper. The cursor is a local object of /// run() whose address never escapes (everything it calls out of line is a /// member of the builder and gets the positions it needs), so that the @@ -1084,7 +1092,7 @@ arr_next: { return false; } - if (TrailingCommas && cur() == ']') + if (enabled(TrailingCommas) && cur() == ']') { ++p; goto close_container; @@ -1151,7 +1159,7 @@ obj_next: { return false; } - if (TrailingCommas && cur() == '}') + if (enabled(TrailingCommas) && cur() == '}') { ++p; goto close_container; @@ -1293,7 +1301,7 @@ indent_done: { ++p; } - if (Comments && cur() == '/') + if (enabled(Comments) && cur() == '/') { const unsigned char* const q = cold.comment(p); if (q == nullptr) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 71cfe2322..8c81b9123 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -279,10 +279,6 @@ if(json_32bit_test_only) elseif(NOT json_32bit_test) list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp) endif() -if(NOT JSON_MultipleHeaders) - # the internal headers of json_view are not part of a single header yet - list(FILTER files EXCLUDE REGEX src/unit-json_view_builder.cpp) -endif() foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 4cfe9e40a..e0cc77724 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -29,6 +29,7 @@ using nlohmann::ordered_json_document; namespace { +#if !defined(JSON_NOEXCEPTION) // the exception parse() throws for a text, or "" if it accepts it std::string parse_exception(const std::string& text, bool comments = false, bool trailing_commas = false) { @@ -57,6 +58,7 @@ std::string view_exception(const std::string& text, bool comments = false, bool } return ""; } +#endif // a small deterministic generator of documents struct generator @@ -146,7 +148,7 @@ TEST_CASE("json_view") CHECK(v.materialize() == j); } - const json_view invalid; + const json_view invalid{}; CHECK(invalid.is_discarded()); CHECK(!static_cast(invalid)); CHECK(invalid.type() == json::value_t::discarded); @@ -191,9 +193,11 @@ TEST_CASE("json_view") }) { CAPTURE(text); +#if !defined(JSON_NOEXCEPTION) const std::string expected = parse_exception(text); REQUIRE(!expected.empty()); CHECK(view_exception(text) == expected); +#endif CHECK(!json_document::accept(text)); const json_document d = json_document::parse(text, false); CHECK(d.is_discarded()); @@ -201,8 +205,9 @@ TEST_CASE("json_view") CHECK(d.node_count() == 0); } // the exception types - CHECK_THROWS_AS(json_document::parse("[1,"), json::parse_error&); - CHECK_THROWS_AS(json_document::parse("1e400"), json::out_of_range&); + json_document _; + CHECK_THROWS_AS(_ = json_document::parse("[1,"), json::parse_error&); + CHECK_THROWS_AS(_ = json_document::parse("1e400"), json::out_of_range&); } SECTION("parse options") @@ -217,7 +222,9 @@ TEST_CASE("json_view") const bool comments = (options & 1) != 0; const bool trailing_commas = (options & 2) != 0; CHECK(json_document::accept(text, comments, trailing_commas) == json::accept(text, comments, trailing_commas)); +#if !defined(JSON_NOEXCEPTION) CHECK(view_exception(text, comments, trailing_commas) == parse_exception(text, comments, trailing_commas)); +#endif } } } @@ -229,7 +236,9 @@ TEST_CASE("json_view") const std::string nul_in_comment("[1, // c\0\n2]", 12); CHECK(json_document::accept(nul_in_comment, true) == json::accept(nul_in_comment, true)); CHECK(json_document::parse("\xEF\xBB\xBF[1]").root().materialize() == json::parse("\xEF\xBB\xBF[1]")); +#if !defined(JSON_NOEXCEPTION) CHECK(view_exception("\xEF\xBB") == parse_exception("\xEF\xBB")); +#endif } SECTION("inputs") diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 4584e8728..889067bae 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -9,8 +9,12 @@ #include "doctest_compatibility.h" #include -#include -#include +#if JSON_TEST_USING_MULTIPLE_HEADERS + #include + #include +#else + #include // the single header contains the internal headers +#endif using nlohmann::json; #include From 958fbc3702522dda1ba4850b2963d3c7bae58dd5 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:41:59 +0200 Subject: [PATCH 05/13] Fix CI: json_view access tests without exceptions and on clang 3.6 - ci_test_noexceptions: the element access tests compare the exceptions of json_view and basic_json through exception_of(), which catches them outside a CHECK_THROWS; with JSON_NOEXCEPTION the first one aborted the test. Compile those comparisons only with exceptions. - clang 3.6: value-initialize a const json_view, as in the tests of json-view/10-view-document. - Format three new documentation examples with the pinned astyle, which the "check" job runs once it gets past the amalgamation step. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp | 2 +- docs/mkdocs/docs/examples/basic_json_view__cend.cpp | 2 +- docs/mkdocs/docs/examples/basic_json_view__type_name.cpp | 5 ++++- tests/src/unit-json_view.cpp | 6 +++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp b/docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp index 811db6267..4e67a8041 100644 --- a/docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp +++ b/docs/mkdocs/docs/examples/basic_json_view__cbegin.cpp @@ -15,7 +15,7 @@ int main() const auto values = measurements.root(); const int sum = std::accumulate(values.cbegin(), values.cend(), 0, - [](int total, const json_view & v) + [](int total, const json_view & v) { return total + v.materialize().get(); }); diff --git a/docs/mkdocs/docs/examples/basic_json_view__cend.cpp b/docs/mkdocs/docs/examples/basic_json_view__cend.cpp index 2999bd684..c72990673 100644 --- a/docs/mkdocs/docs/examples/basic_json_view__cend.cpp +++ b/docs/mkdocs/docs/examples/basic_json_view__cend.cpp @@ -15,7 +15,7 @@ int main() const auto records = batch.root(); const bool all_objects = std::all_of(records.cbegin(), records.cend(), - [](const json_view & v) + [](const json_view & v) { return v.is_object(); }); diff --git a/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp b/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp index 9d59792ca..30661c0fd 100644 --- a/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp +++ b/docs/mkdocs/docs/examples/basic_json_view__type_name.cpp @@ -13,7 +13,10 @@ int main() json_document bad = json_document::parse("[1, 2, 3]"); json_document failed = json_document::parse("not json", /* allow_exceptions */ false); - for (const json_view v : { good.root(), bad.root(), failed.root() }) + for (const json_view v : + { + good.root(), bad.root(), failed.root() + }) { if (v.is_object()) { diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 9f0516870..d1c05b41f 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -387,6 +387,7 @@ TEST_CASE("json_view") namespace { +#if !defined(JSON_NOEXCEPTION) // the exception a call throws, or "" if it throws none template std::string exception_of(F f) @@ -401,6 +402,7 @@ std::string exception_of(F f) } return ""; } +#endif // compares a view with the ordered_json value materialize() gives for it: // types, sizes, elements and members (by index, key, and iteration), in @@ -569,6 +571,7 @@ TEST_CASE("json_view element access and iteration") const json_document d = json_document::parse(text); const json_view v = d.root(); const json j = v.materialize(); +#if !defined(JSON_NOEXCEPTION) if (!j.is_object()) { CHECK(exception_of([&] { static_cast(v["a"]); }) == exception_of([&] { static_cast(j["a"]); })); @@ -590,6 +593,7 @@ TEST_CASE("json_view element access and iteration") CHECK(exception_of([&] { static_cast(v.front()); }) == exception_of([&] { static_cast(j.front()); })); CHECK(exception_of([&] { static_cast(v.back()); }) == exception_of([&] { static_cast(j.back()); })); } +#endif CHECK(v.contains("a") == j.contains("a")); CHECK(v.count("a") == j.count("a")); CHECK((v.find("a") == v.end()) == (j.find("a") == j.end())); // NOLINT(readability-container-contains): find() is what is tested @@ -601,7 +605,7 @@ TEST_CASE("json_view element access and iteration") CHECK(!d.root()["a"][0]); CHECK_THROWS_WITH_AS(d.root()["a"].front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); CHECK_THROWS_WITH_AS(d.root()["a"].back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); - const json_view invalid; + const json_view invalid{}; CHECK(invalid.begin() == invalid.end()); CHECK(std::string(invalid.type_name()) == "discarded"); CHECK_THROWS_WITH_AS(invalid["a"], "[json.exception.type_error.305] cannot use operator[] with a string argument with discarded", json::type_error&); From a210437b301142ff170d27e06089cf09e53f6795 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:44:33 +0200 Subject: [PATCH 06/13] Fix CI: json_view value tests without exceptions and with GCC - ci_test_noexceptions: exception_of() and without_path() exist only with exceptions (they catch outside a CHECK_THROWS, which aborts with JSON_NOEXCEPTION); compile the comparisons of the conversion, value(), and JSON pointer errors only with exceptions as well. - ci_test_gcc: -Werror=unused-result for static_cast(j.contains(p)) (GCC's warn_unused_result ignores a cast to void); store the result. Signed-off-by: Niels Lohmann --- tests/src/unit-json_view.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 2587401bb..8d825e606 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -671,6 +671,7 @@ TEST_CASE("json_view element access and iteration") namespace { +#if !defined(JSON_NOEXCEPTION) // an exception message without the context that basic_json adds with // JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) "); // the view's exceptions have no such context @@ -688,6 +689,7 @@ std::string without_path(std::string msg) } return msg; } +#endif // the bits of a float, to compare values bit for bit std::uint64_t bits(double x) @@ -767,6 +769,7 @@ void check_values(const ordered_json_view& v, const ordered_json& j, const std:: break; } +#if !defined(JSON_NOEXCEPTION) // conversions to the wrong type throw what basic_json throws if (!j.is_number()) { @@ -783,6 +786,7 @@ void check_values(const ordered_json_view& v, const ordered_json& j, const std:: { CHECK(exception_of([&] { static_cast(v.get>()); }) == without_path(exception_of([&] { static_cast(j.get>()); }))); } +#endif if (v.is_array()) { @@ -959,10 +963,12 @@ TEST_CASE("json_view values") // with a JSON pointer, arrays can be asked as well CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0)); CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3)); +#if !defined(JSON_NOEXCEPTION) CHECK(exception_of([&] { static_cast(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast(j["o"]["x"].value("k", 0)); }))); CHECK(exception_of([&] { static_cast(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast(j.value("s", 0)); }))); CHECK(exception_of([&] { static_cast(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast(j["n"].value("x", 0)); }))); CHECK(exception_of([&] { static_cast(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast(j["n"].value(json::json_pointer("/x"), 0)); }))); +#endif } } @@ -1001,6 +1007,7 @@ TEST_CASE("json_view JSON pointers") } } +#if !defined(JSON_NOEXCEPTION) SECTION("errors are those of basic_json") { const std::string text = R"({"a": [1, {"b": null}], "c": "s", "": {"": 0}, "a~b": 1, "c/d": 2})"; @@ -1033,9 +1040,14 @@ TEST_CASE("json_view JSON pointers") // (basic_json::contains() throws out_of_range.404 for an empty // array index token, although it is not meant to throw; the view // answers false) - const std::string contains_error = exception_of([&] { static_cast(j.contains(p)); }); + const std::string contains_error = exception_of([&] + { + const bool found = j.contains(p); + static_cast(found); + }); CHECK(v.contains(p) == (contains_error.empty() && j.contains(p))); CHECK(exception_of([&] { static_cast(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast(j.value(p, 5)); }))); } } +#endif } From 9549dce77b1e057faffcbe205665acec4bb3fb8c Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:44:44 +0200 Subject: [PATCH 07/13] Fix CI: value-initialize a const json_view for clang 3.6 clang 3.6 rejects `const json_view invalid;` (no user-provided default constructor, CWG 253), as fixed in json-view/10-view-document. Signed-off-by: Niels Lohmann --- tests/src/unit-json_view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 8d825e606..708f76b4e 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -942,7 +942,7 @@ TEST_CASE("json_view values") // a duplicate key: the last value, as parse() CHECK((json_document::parse(R"({"a":1,"a":2})").root().get>() == std::map {{"a", 2}})); - const json_view invalid; + const json_view invalid{}; CHECK_THROWS_WITH_AS(invalid.get(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&); CHECK(invalid.get().is_discarded()); } From 5484f2ebbcfcfd28d666759e02680b227e1b0e06 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:46:08 +0200 Subject: [PATCH 08/13] Format the dump() example with the pinned astyle The "check" job runs astyle over the documentation examples once it gets past the amalgamation step. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/examples/basic_json_view__dump.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mkdocs/docs/examples/basic_json_view__dump.cpp b/docs/mkdocs/docs/examples/basic_json_view__dump.cpp index 7a00c06d2..c9bd8198f 100644 --- a/docs/mkdocs/docs/examples/basic_json_view__dump.cpp +++ b/docs/mkdocs/docs/examples/basic_json_view__dump.cpp @@ -19,7 +19,7 @@ int main() // the source text; a json value's object_t is std::map, so // materialize().dump() of the very same view sorts the keys instead const json_document config = json_document::parse( - R"({"name": "cache", "host": "db1", "port": 6379, "timeout": 30})"); + R"({"name": "cache", "host": "db1", "port": 6379, "timeout": 30})"); std::cout << config.root().dump(2) << "\n\n"; std::cout << config.root().materialize().dump(2) << '\n'; } From 5ba6ea1f6f83e09262d2e66ed01ed792774d935b Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:47:16 +0200 Subject: [PATCH 09/13] Format the comparison examples with the pinned astyle The "check" job runs astyle over the documentation examples once it gets past the amalgamation step. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp | 6 +++--- docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp b/docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp index ac59838f4..846d40fc0 100644 --- a/docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp +++ b/docs/mkdocs/docs/examples/basic_json_view__operator_eq.cpp @@ -10,9 +10,9 @@ int main() // directly as views, without ever building a nlohmann::json value for // either one const json_document previous = json_document::parse( - R"({"name": "cache", "port": 6379, "timeout": 30})"); + R"({"name": "cache", "port": 6379, "timeout": 30})"); const json_document current = json_document::parse( - R"({"port": 6379.0, "timeout": 30, "name": "cache"})"); + R"({"port": 6379.0, "timeout": 30, "name": "cache"})"); // same members, reordered, and 6379 written as a float -- operator== // treats them the same way BasicJsonType::operator== would @@ -20,7 +20,7 @@ int main() // an actually changed value is detected the same way const json_document changed = json_document::parse( - R"({"name": "cache", "port": 6380, "timeout": 30})"); + R"({"name": "cache", "port": 6380, "timeout": 30})"); std::cout << (previous.root() == changed.root()) << '\n'; // comparing a view directly against an expected json value -- handy in a diff --git a/docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp b/docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp index e284ae1a1..faa1d61cf 100644 --- a/docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp +++ b/docs/mkdocs/docs/examples/basic_json_view__operator_ne.cpp @@ -11,7 +11,7 @@ int main() // unwanted shape -- without ever materializing it into a json value just // to compare const json_document received = json_document::parse( - R"({"status": "ok", "code": 200})"); + R"({"status": "ok", "code": 200})"); const json unwanted = {{"status", "error"}, {"code", 500}}; std::cout << std::boolalpha << (received.root() != unwanted) << '\n'; From ab4f6461b8d26168b9b2d12bcc5c2e7e5559c0a0 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 22:56:26 +0200 Subject: [PATCH 10/13] Fix CI: useless cast in the growth of the view's node index GCC -Werror=useless-cast (ci_test_gcc on Linux x86-64) rejected static_cast(guess + (guess / 4) + 64): the sum is a std::uint64_t prvalue, the same type as std::size_t there, while the cast is needed where std::size_t is 32 bits wide. Cast a named variable instead, which GCC does not report. The build stopped at an earlier error before, so the previous CI run did not show this one. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/builder.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 2e0151b31..8393b9c8d 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -195,8 +195,9 @@ class builder { const std::uint64_t done = static_cast(at - b) + 1; const std::uint64_t guess = static_cast(n) * static_cast(e - b + 1) / done; + const std::uint64_t grown = guess + (guess / 4) + 64; // a variable: GCC calls a cast of the sum useless where std::uint64_t is std::size_t doc.tape_size = n; - doc.reserve((std::max)(static_cast(guess + (guess / 4) + 64), n + (n / 2) + 64)); + doc.reserve((std::max)(static_cast(grown), n + (n / 2) + 64)); return doc.tape; } From dbbe96c42cbd69f889fd1b3b6e13d101d678ca74 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 23:05:48 +0200 Subject: [PATCH 11/13] Fix CI: useless cast in the array index check of the view's JSON pointers GCC -Werror=useless-cast on Linux x86-64 rejected static_cast((std::numeric_limits::max)()), as both are the same type there. Compare without the cast: std::size_t converts to std::uint64_t implicitly on every platform. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/pointer.hpp | 2 +- single_include/nlohmann/json_view.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/view/pointer.hpp b/include/nlohmann/detail/view/pointer.hpp index 2c7b1e4db..d2ac497d7 100644 --- a/include/nlohmann/detail/view/pointer.hpp +++ b/include/nlohmann/detail/view/pointer.hpp @@ -69,7 +69,7 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept } v = (v * 10) + d; } - if (v >= static_cast((std::numeric_limits::max)())) + if (v >= (std::numeric_limits::max)()) // (std::size_t converts to std::uint64_t implicitly) { return index_status::too_large; } diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index bc5391894..627c64ab4 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -2464,7 +2464,7 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept } v = (v * 10) + d; } - if (v >= static_cast((std::numeric_limits::max)())) + if (v >= (std::numeric_limits::max)()) // (std::size_t converts to std::uint64_t implicitly) { return index_status::too_large; } From 94cd91c5059e068a4e6f13a4910a5dd04cab9338 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 23:16:38 +0200 Subject: [PATCH 12/13] Fix CI: read the json_view.hpp amalgamation config from the pull request The "check" job (Check amalgamation) runs develop's amalgamate.py and read all configurations from the develop checkout, where config_json_view.json does not exist until this stack lands, so it failed with FileNotFoundError. Read that configuration from the pull request's checkout; the tool itself stays develop's. Signed-off-by: Niels Lohmann --- .github/workflows/check_amalgamation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index 5a7efb07f..649d26907 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -67,7 +67,9 @@ jobs: python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json.json -s . python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json_fwd.json -s . - python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json_view.json -s . + # the configuration of json_view.hpp comes with the pull request until + # it is on develop; the tool itself is still develop's + python3 $TOOL_DIR/amalgamate.py -c $MAIN_DIR/tools/amalgamate/config_json_view.json -s . # the header list of the Bazel "json" target must match the files in include/ cmake -P cmake/scripts/gen_bazel_build_file.cmake From fa58c6e6daa749f2712bf0da4c8324545fbb492e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 23:35:32 +0200 Subject: [PATCH 13/13] Fix CI: useless casts of the key hash of the view's object index GCC -Werror=useless-cast on Linux x86-64 rejects static_cast(key_hash(...)): the call returns a std::uint64_t prvalue, the same type as std::size_t there, while the cast is needed where std::size_t is 32 bits wide. Store the hash in a variable and cast that, which GCC does not report. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/object_index.hpp | 6 ++++-- single_include/nlohmann/json_view.hpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/view/object_index.hpp b/include/nlohmann/detail/view/object_index.hpp index 24c5d9ece..27348d827 100644 --- a/include/nlohmann/detail/view/object_index.hpp +++ b/include/nlohmann/detail/view/object_index.hpp @@ -71,7 +71,8 @@ inline void build_object_index(document_data& d, node* obj) for (const node* k = document_data::first_child(obj), *end = document_data::child_end(obj); k != end; k = document_data::after(k + 1)) { const char* const key = d.str(*k); - std::size_t i = static_cast(key_hash(key, k->len)) & mask; + const std::uint64_t hash = key_hash(key, k->len); // (a cast of the call would be useless where std::uint64_t is std::size_t) + std::size_t i = static_cast(hash) & mask; bool duplicate = false; while (slots[i] != 0) { @@ -107,7 +108,8 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c { const document_data::object_index& ix = d.indexes[obj->extra - 1u]; const std::uint32_t* const slots = d.index_slots.data() + ix.start; - std::size_t i = static_cast(key_hash(key, n)) & ix.mask; + const std::uint64_t hash = key_hash(key, n); // (a cast of the call would be useless where std::uint64_t is std::size_t) + std::size_t i = static_cast(hash) & ix.mask; for (;;) { const std::uint32_t s = slots[i]; diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 93ca903d4..40ec3e89f 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -2789,7 +2789,8 @@ inline void build_object_index(document_data& d, node* obj) for (const node* k = document_data::first_child(obj), *end = document_data::child_end(obj); k != end; k = document_data::after(k + 1)) { const char* const key = d.str(*k); - std::size_t i = static_cast(key_hash(key, k->len)) & mask; + const std::uint64_t hash = key_hash(key, k->len); // (a cast of the call would be useless where std::uint64_t is std::size_t) + std::size_t i = static_cast(hash) & mask; bool duplicate = false; while (slots[i] != 0) { @@ -2825,7 +2826,8 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c { const document_data::object_index& ix = d.indexes[obj->extra - 1u]; const std::uint32_t* const slots = d.index_slots.data() + ix.start; - std::size_t i = static_cast(key_hash(key, n)) & ix.mask; + const std::uint64_t hash = key_hash(key, n); // (a cast of the call would be useless where std::uint64_t is std::size_t) + std::size_t i = static_cast(hash) & ix.mask; for (;;) { const std::uint32_t s = slots[i];