from_json: number-first dispatch for arithmetic types, loop for std::array (#5891)

from_json(ArithmeticType&) now reads numbers through get_number_value() and
handles a boolean on a cold path, which puts the three number types first in
the per-element type dispatch.

get<std::array<T, N>>() always took the identity_tag overload, a pack
expansion of N calls j.at(i).get<T>() that clang unrolls into straight-line
code (about 28,000 instructions for N = 1000), so its speed depended on
inlining decisions in that body. For trivial element types, which cannot tell
default construction plus assignment from construction in place, it now fills
a std::array in a loop. That loop, shared with get_to(), checks the size once
and reads the inner array directly; a short array still converts the elements
it has and then throws the out_of_range error at() would throw.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-23 16:00:12 +02:00
1 parent 0e5a906cd5
commit 21bc758b89
3 files changed
+140 -88

No files matched your search

@@ -8,7 +8,7 @@
#pragma once
#include <algorithm> // transform
#include <algorithm> // min, transform
#include <array> // array
#include <forward_list> // forward_list
#include <iterator> // inserter, front_inserter, end
@@ -256,15 +256,30 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType:
arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
}
// fill a std::array from the JSON array j: the size is checked once and the
// elements are read in order, so a short array converts the elements it has and
// then throws the out_of_range error at() would throw for the first missing one
template<typename BasicJsonType, typename T, std::size_t N>
void from_json_std_array(const BasicJsonType& j, std::array<T, N>& arr)
{
const auto& inner_array = *j.template get_ptr<const typename BasicJsonType::array_t*>();
const std::size_t size = (std::min)(N, inner_array.size());
for (std::size_t i = 0; i < size; ++i)
{
arr[i] = inner_array[i].template get<T>();
}
if (JSON_HEDLEY_UNLIKELY(size < N))
{
static_cast<void>(j.at(size));
}
}
template<typename BasicJsonType, typename T, std::size_t N>
auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
priority_tag<2> /*unused*/)
-> decltype(j.template get<T>(), void())
{
for (std::size_t i = 0; i < N; ++i)
{
arr[i] = j.at(i).template get<T>();
}
from_json_std_array(j, arr);
}
// reserve() is called through this pair (used for arrays and objects alike)
@@ -347,6 +362,35 @@ std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(const BasicJsonType&
return { { j.at(Idx).template get<T>()... } };
}
// std::is_trivial is deprecated in C++26 (see binary_writer::to_char_type)
#ifdef JSON_HAS_CPP_26
template<typename T>
using is_trivial_element = conjunction<std::is_trivially_copyable<T>, std::is_trivially_default_constructible<T>>;
#else
template<typename T>
using is_trivial_element = std::is_trivial<T>;
#endif
// the element type may not be default constructible: construct every element
// in place (a pack expansion that is unrolled N times)
template < typename BasicJsonType, typename T, std::size_t N >
std::array<T, N> from_json_inplace_array(const BasicJsonType& j, identity_tag<std::array<T, N>> tag,
std::false_type /*is_trivial_element*/)
{
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
}
// a trivial element type cannot tell default construction plus assignment from
// construction in place, so use a loop instead of unrolling N conversions
template < typename BasicJsonType, typename T, std::size_t N >
std::array<T, N> from_json_inplace_array(const BasicJsonType& j, identity_tag<std::array<T, N>> /*unused*/,
std::true_type /*is_trivial_element*/)
{
std::array<T, N> arr; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): every element is assigned or an exception is thrown
from_json_std_array(j, arr);
return arr;
}
template < typename BasicJsonType, typename T, std::size_t N >
auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}))
@@ -356,7 +400,7 @@ auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
throw_type_must_be("array", j);
}
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
return from_json_inplace_array(j, tag, is_trivial_element<T> {});
}
template<typename BasicJsonType>
@@ -437,46 +481,17 @@ template < typename BasicJsonType, typename ArithmeticType,
int > = 0 >
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
{
// the number cases repeat get_number_value() on purpose: calling it here
// changes the inlining of get<T>() in the std::array conversion, which then
// runs 2.4x slower (GetArray benchmark, #5891); unlike get_arithmetic_value(),
// a boolean is accepted
switch (static_cast<value_t>(j))
if (get_number_value(j, val))
{
case value_t::number_unsigned:
{
// never null: a basic_json whose number_unsigned_t is number_integer_t
// stores no number_unsigned values (#2573)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr);
break;
}
case value_t::number_integer:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
break;
}
case value_t::number_float:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
break;
}
case value_t::boolean:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
break;
}
case value_t::null:
case value_t::object:
case value_t::array:
case value_t::string:
case value_t::binary:
case value_t::discarded:
default:
throw_type_must_be("number", j);
return;
}
// unlike get_arithmetic_value(), a boolean is accepted here
if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))
{
throw_type_must_be("number", j);
}
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
}
template<typename BasicJsonType, typename Type>