From ad4c240e8b9cf90132a4775accfb774e934fbdf2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Oct 2026 08:17:22 +0200 Subject: [PATCH] Speed up array-to-container from_json Signed-off-by: Niels Lohmann --- .../nlohmann/detail/conversions/from_json.hpp | 28 ++++++++++++++----- single_include/nlohmann/json.hpp | 28 ++++++++++++++----- tests/benchmarks/src/benchmarks.cpp | 26 +++++++++++++++++ 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 0332474b2..a2c82923c 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -266,6 +266,22 @@ template void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) {} +// append through push_back() where available; containers without it (such as +// std::set) fall back to insert(end(), ...), which is what std::inserter did +template +auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/) +-> decltype(arr.push_back(std::forward(value)), void()) +{ + arr.push_back(std::forward(value)); +} + +template +void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/) +{ + using std::end; + arr.insert(end(arr), std::forward(value)); +} + template::value, @@ -275,17 +291,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p j.template get(), void()) { - using std::end; - ConstructibleArrayType ret; - from_json_array_reserve(ret, j.size(), priority_tag<1> {}); - std::transform(j.begin(), j.end(), - std::inserter(ret, end(ret)), [](const BasicJsonType & i) + const auto* inner_array = j.template get_ptr(); + from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {}); + for (const auto& elem : *inner_array) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); - }); + from_json_array_append(ret, elem.template get(), priority_tag<1> {}); + } arr = std::move(ret); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 7a3b0a2f6..4585ab440 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5970,6 +5970,22 @@ template void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) {} +// append through push_back() where available; containers without it (such as +// std::set) fall back to insert(end(), ...), which is what std::inserter did +template +auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/) +-> decltype(arr.push_back(std::forward(value)), void()) +{ + arr.push_back(std::forward(value)); +} + +template +void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/) +{ + using std::end; + arr.insert(end(arr), std::forward(value)); +} + template::value, @@ -5979,17 +5995,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p j.template get(), void()) { - using std::end; - ConstructibleArrayType ret; - from_json_array_reserve(ret, j.size(), priority_tag<1> {}); - std::transform(j.begin(), j.end(), - std::inserter(ret, end(ret)), [](const BasicJsonType & i) + const auto* inner_array = j.template get_ptr(); + from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {}); + for (const auto& elem : *inner_array) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); - }); + from_json_array_append(ret, elem.template get(), priority_tag<1> {}); + } arr = std::move(ret); } diff --git a/tests/benchmarks/src/benchmarks.cpp b/tests/benchmarks/src/benchmarks.cpp index 5df7dd473..edc4f6088 100644 --- a/tests/benchmarks/src/benchmarks.cpp +++ b/tests/benchmarks/src/benchmarks.cpp @@ -9,7 +9,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -621,4 +624,27 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000); +// conversion of a JSON array into standard containers +template +static void GetArray(benchmark::State& state) +{ + json j = json::array(); + for (int i = 0; i < 1000; ++i) + { + j.push_back(i); + } + + for (auto _ : state) + { + auto c = j.get(); + benchmark::DoNotOptimize(c); + } + state.SetItemsProcessed(state.iterations() * 1000); +} + +BENCHMARK_TEMPLATE(GetArray, std::vector); +BENCHMARK_TEMPLATE(GetArray, std::deque); +BENCHMARK_TEMPLATE(GetArray, std::set); +BENCHMARK_TEMPLATE(GetArray, std::array); + BENCHMARK_MAIN();