From c09fa3047dc48e52adb4ec283c1fe359c83d01cd Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 22:20:55 +0200 Subject: [PATCH] Make the index text of items() on arrays lazy basic_json_view::items() on an array formatted every element's index with std::to_string when the item was made, whether or not the loop asks for the key. The item now keeps the position and makes the text on the first key() call. Objects are not affected. Iterating over the items of an array of 3000 numbers and reading only the values: Apple M1: 25.5 us -> 4.6 us (0.45 M -> ~0 net instructions) Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 14.9 M -> 4.1 M / 15.3 M -> 4.6 M Reading key() costs what it did (24.5 us). Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/iterator.hpp | 18 ++++++++++-------- single_include/nlohmann/json_view.hpp | 18 ++++++++++-------- tests/src/unit-json_view.cpp | 22 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/include/nlohmann/detail/view/iterator.hpp b/include/nlohmann/detail/view/iterator.hpp index 12906e9c1..069f50fd1 100644 --- a/include/nlohmann/detail/view/iterator.hpp +++ b/include/nlohmann/detail/view/iterator.hpp @@ -147,20 +147,21 @@ class view_item view_item(const iterator& it, std::size_t index) : m_it(it) - { - if (!it.is_object_iterator()) - { - m_index = std::to_string(index); - } - } + , m_position(index) + {} - /// the member key, or the element index for arrays + /// the member key, or the element index for arrays (as text made on the + /// first call, so that iterating over the values pays nothing for it) string_view_t key() const { if (m_it.is_object_iterator()) { return m_it.key(); } + if (m_index.empty()) + { + m_index = std::to_string(m_position); + } return string_view_t(m_index.data(), m_index.size()); } @@ -183,7 +184,8 @@ class view_item private: iterator m_it; - std::string m_index{}; // NOLINT(readability-redundant-member-init) + std::size_t m_position; ///< the index of an array element + mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init) }; /// the range returned by basic_json_view::items() diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 0207ba7e2..0b2365a00 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -5646,20 +5646,21 @@ class view_item view_item(const iterator& it, std::size_t index) : m_it(it) - { - if (!it.is_object_iterator()) - { - m_index = std::to_string(index); - } - } + , m_position(index) + {} - /// the member key, or the element index for arrays + /// the member key, or the element index for arrays (as text made on the + /// first call, so that iterating over the values pays nothing for it) string_view_t key() const { if (m_it.is_object_iterator()) { return m_it.key(); } + if (m_index.empty()) + { + m_index = std::to_string(m_position); + } return string_view_t(m_index.data(), m_index.size()); } @@ -5682,7 +5683,8 @@ class view_item private: iterator m_it; - std::string m_index{}; // NOLINT(readability-redundant-member-init) + std::size_t m_position; ///< the index of an array element + mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init) }; /// the range returned by basic_json_view::items() diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index b7e3ee374..c873e3f55 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -1113,6 +1113,28 @@ TEST_CASE("json_view element access and iteration") CHECK((*first++).key() == "0"); CHECK((*first).key() == "1"); CHECK(++first == items.end()); + + // the keys of array elements are their indexes, whether or not they are asked for + std::string numbers = "[0"; + for (int i = 1; i < 1234; ++i) + { + numbers += "," + std::to_string(i); + } + const json_document array = json_document::parse(numbers + "]"); + std::size_t position = 0; + for (const auto& item : array.root().items()) + { + CHECK(item.value().get() == position); + if (position % 3 != 0) // (not for every item: values alone do not make the key) + { + CHECK(item.key() == std::to_string(position)); + CHECK(item.key().data() == item.key().data()); // (the same text every time) + const json_view::item copy = item; + CHECK(copy.key() == std::to_string(position)); + } + ++position; + } + CHECK(position == 1234); #ifdef JSON_HAS_CPP_17 std::string pairs; for (const auto [key, value] : d.root().items())