From ba25f9616920a5c79fe23fa81e032443a909e202 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 21:28:07 +0200 Subject: [PATCH] Reserve the storage of an ordered_json in flatten() once flatten() appended the entries to an ordered_map one by one, so its vector grew by doubling and moved every entry about once in total. The number of entries is known after a walk over the value that only counts (it needs no path strings), so objects with a reserve() are given that capacity first. std::map has no reserve() and is unchanged. flatten() of an ordered_json (instructions on macOS, Linux Ir GCC 13 / Clang 18): citm_catalog 36.8M -> 23.1M, 32.7M -> 17.1M / 31.7M -> 16.8M; twitter 20.5M -> 13.6M, 17.6M -> 10.0M / 17.0M -> 9.8M. Output is identical. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 47 ++++++++++++++++++++++++ single_include/nlohmann/json.hpp | 47 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index d421a8e0f..5c9fd3836 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -1354,6 +1354,52 @@ class json_pointer obj[key] = std::forward(v); } + /// the number of entries that flattening @a value adds: its primitive + /// values and empty containers + template + static std::size_t count_flattened(const BasicJsonType& value) + { + std::size_t count = 0; + std::vector pending(1, &value); + while (!pending.empty()) + { + const BasicJsonType* const v = pending.back(); + pending.pop_back(); + if (v->is_array() && !v->m_data.m_value.array->empty()) + { + for (const auto& element : *v->m_data.m_value.array) + { + pending.push_back(&element); + } + } + else if (v->is_object() && !v->m_data.m_value.object->empty()) + { + for (const auto& member : *v->m_data.m_value.object) + { + pending.push_back(&member.second); + } + } + else + { + ++count; + } + } + return count; + } + + // Reserve the storage for @a value of objects that have a reserve(), so + // that it does not have to be moved while it grows + template + static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/) + -> decltype(obj.reserve(std::size_t {}), void()) + { + obj.reserve(count_flattened(value)); + } + + template + static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/) + {} + /// append the decimal representation of @a i to @a out static void append_index(string_t& out, std::size_t i) { @@ -1463,6 +1509,7 @@ class json_pointer } }; + reserve_flattened(object, value, detail::priority_tag<1> {}); enter(value); while (!stack.empty()) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 12846dedc..e53b6ec4c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -23704,6 +23704,52 @@ class json_pointer obj[key] = std::forward(v); } + /// the number of entries that flattening @a value adds: its primitive + /// values and empty containers + template + static std::size_t count_flattened(const BasicJsonType& value) + { + std::size_t count = 0; + std::vector pending(1, &value); + while (!pending.empty()) + { + const BasicJsonType* const v = pending.back(); + pending.pop_back(); + if (v->is_array() && !v->m_data.m_value.array->empty()) + { + for (const auto& element : *v->m_data.m_value.array) + { + pending.push_back(&element); + } + } + else if (v->is_object() && !v->m_data.m_value.object->empty()) + { + for (const auto& member : *v->m_data.m_value.object) + { + pending.push_back(&member.second); + } + } + else + { + ++count; + } + } + return count; + } + + // Reserve the storage for @a value of objects that have a reserve(), so + // that it does not have to be moved while it grows + template + static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/) + -> decltype(obj.reserve(std::size_t {}), void()) + { + obj.reserve(count_flattened(value)); + } + + template + static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/) + {} + /// append the decimal representation of @a i to @a out static void append_index(string_t& out, std::size_t i) { @@ -23813,6 +23859,7 @@ class json_pointer } }; + reserve_flattened(object, value, detail::priority_tag<1> {}); enter(value); while (!stack.empty()) {