From c25e40811120cd70bd7cf35b3809e6d7b661ecff Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 9 Oct 2026 16:11:27 +0200 Subject: [PATCH] Release the spare capacity of the hash index in shrink_to_fit shrink_to_fit() now trims the tables of large objects like the node array and the decoded strings, and the list of large objects is released as soon as the tables are built. Signed-off-by: Niels Lohmann --- include/nlohmann/json_view.hpp | 12 ++++++++++ tests/src/unit-json_view.cpp | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 817476c1e..ba9f4037f 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -1015,6 +1015,8 @@ class basic_json_document std::vector arena(shrink_arena ? d.arena : std::vector()); const bool shrink_nodes = d.nodes != d.inline_nodes && d.node_count != d.node_capacity; const bool into_header = d.node_count <= d.inline_capacity; + std::vector indexes(d.indexes.capacity() > d.indexes.size() ? d.indexes : std::vector()); + std::vector index_slots(d.index_slots.capacity() > d.index_slots.size() ? d.index_slots : std::vector()); node* fresh = (shrink_nodes && !into_header) ? static_cast(::operator new (d.node_count * sizeof(node))) : d.inline_nodes; if (shrink_nodes) @@ -1029,6 +1031,14 @@ class basic_json_document d.arena.swap(arena); d.base[1] = d.arena.data(); } + if (d.indexes.capacity() > d.indexes.size()) + { + d.indexes.swap(indexes); + } + if (d.index_slots.capacity() > d.index_slots.size()) + { + d.index_slots.swap(index_slots); + } } private: @@ -1107,9 +1117,11 @@ class basic_json_document d.base[0] = d.src; d.base[1] = d.arena.data(); detail::view::build_object_indexes(d); + std::vector().swap(d.large_objects); // (only needed while parsing) d.discarded = false; return; } + std::vector().swap(d.large_objects); if (allow_exceptions) { detail::view::throw_parse_failure(failure, src, size, comments, trailing_commas); diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 6a869a257..b903d1476 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -1718,6 +1718,48 @@ TEST_CASE("json_view large objects") CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (slots * sizeof(std::uint32_t))); } + SECTION("shrink_to_fit releases the tables' spare capacity") + { + const auto make_text = [](int objects, int members) + { + std::string text = "["; + for (int object = 0; object < objects; ++object) + { + text += object != 0 ? ",{" : "{"; + for (int i = 0; i < members + object; ++i) + { + text += (i != 0 ? ",\"" : "\"") + std::to_string(i) + "\":" + std::to_string(i); + } + text += '}'; + } + return text + "]"; + }; + const std::string small_text = make_text(5, 150); + const std::string big_text = make_text(40, 400); + + // reading a big text, and then a small one, leaves the spare capacity + // of the big one: shrink_to_fit() brings the document to the size of + // one parsed from the small text alone + json_document d = json_document::parse(big_text); + const std::size_t big = d.memory_usage(); + d.read(small_text); + CHECK(d.memory_usage() >= big); + d.shrink_to_fit(); + json_document fresh = json_document::parse(small_text); + fresh.shrink_to_fit(); + CHECK(d.memory_usage() == fresh.memory_usage()); + CHECK(d.memory_usage() < big / 2); + CHECK(d.root() == json::parse(small_text)); + for (int object = 0; object < 5; ++object) + { + const json_view v = d.root()[static_cast(object)]; + for (int i = 0; i < 150 + object; ++i) + { + CHECK(v[std::to_string(i)].get() == i); + } + } + } + SECTION("nested, reused, and in arrays") { std::string inner = "{";