Only reserve array capacity if the array type supports it

PR #5476 added an unconditional `reserve()` call to the `start_array()`
implementations of both `json_sax_dom_parser` and
`json_sax_dom_callback_parser`. `ArrayType` is a template parameter of
`basic_json`, however, and is not required to have a `reserve()` member
function: instantiating either parser for, e.g., `basic_json<std::map,
std::deque>` fails to compile, which breaks every use of `parse()` and
the binary readers for such a type.

Move the capped reservation into a `reserve_array()` helper that selects
a no-op overload through `priority_tag` when `ArrayType` has no
`reserve()`, mirroring `from_json_object_reserve()`. `std::vector` array
types keep reserving as before, and the 16384-element cap is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Niels Lohmann
2026-09-13 18:02:01 +02:00
co-authored by Claude Opus 5
parent c0b2878a44
commit ee211df64a
3 changed files with 99 additions and 24 deletions
+46
View File
@@ -27,6 +27,7 @@ using ordered_json = nlohmann::ordered_json;
#endif
#include <cstdio>
#include <deque>
#include <list>
#include <type_traits>
#include <utility>
@@ -896,4 +897,49 @@ TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with
}
TEST_CASE("regression test #5476 - array type without reserve()")
{
// the capacity reserved for definite-length arrays must not require the
// array type to have a reserve() member function
using deque_json = nlohmann::basic_json<std::map, std::deque>;
SECTION("std::deque")
{
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
// the binary formats pass a definite length to start_array()
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
CHECK(deque_json::from_msgpack(deque_json::to_msgpack(j)) == j);
// parse() instantiates the callback parser as well, which reserves too
const auto with_callback = deque_json::parse(R"([1,2,3])", [](int /*depth*/, deque_json::parse_event_t /*event*/, deque_json& /*parsed*/)
{
return true;
});
CHECK(with_callback == deque_json({1, 2, 3}));
}
SECTION("std::vector still reserves")
{
json array = json::array();
for (int i = 0; i < 100; ++i)
{
array.push_back(i);
}
const auto j = json::from_cbor(json::to_cbor(array));
CHECK(j == array);
CHECK(j.get_ref<const json::array_t&>().capacity() >= 100);
}
SECTION("the reservation stays capped")
{
// CBOR array announcing 2^32-1 elements, but truncated right after the
// header: the input must be rejected without reserving that capacity
const std::vector<std::uint8_t> truncated = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
CHECK(json::from_cbor(truncated, true, false).is_discarded());
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP