From de393408328d46ee4e7edbe41390f784154da680 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 d70bde43f..33ee4d086 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -951,6 +951,8 @@ class basic_json_document std::string arena(shrink_arena ? d.arena : std::string()); const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap; const bool into_header = d.tape_size <= d.inline_cap; + 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_tape && !into_header) ? static_cast(::operator new (d.tape_size * sizeof(node))) : d.inline_tape; if (shrink_tape) @@ -965,6 +967,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: @@ -1019,9 +1029,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 a4fb9dcba..1711275eb 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -1381,6 +1381,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 = "{";