Scan reference tokens and array indices in place in resolve_plain()

Find the end of a reference token and reject an escape in one loop over the
characters instead of two library calls (find_first_of, memchr), and parse an
array index straight from the pointer instead of copying the token first. An
index that is not a plain number of up to 18 digits (9 for a 32-bit
size_type) is left to json_pointer, which reports it as before.

patch() on the diff of every eighth scalar (instructions on macOS, Linux Ir GCC 13 /
Clang 18): canada 67.7M -> 63.7M, 42.6M -> 38.4M / 43.6M -> 40.0M; citm_catalog
27.1M -> 26.8M, 18.1M -> 17.7M / 18.0M -> 17.7M; twitter unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:29:44 +02:00
1 parent bd4760322f
commit 38ba507196
2 files changed
+62 -32

No files matched your search

+31 -16
View File
@@ -795,12 +795,6 @@ class json_pointer
}
cache.path = &path;
// the resolved prefix has no "~"
if (JSON_HEDLEY_UNLIKELY(length > steps[depth - 1].first && std::memchr(path.data() + steps[depth - 1].first, '~', length - steps[depth - 1].first) != nullptr))
{
return nullptr;
}
BasicJsonType* ptr = steps[depth - 1].second;
for (std::size_t pos = steps[depth - 1].first; pos < length;)
{
@@ -809,11 +803,18 @@ class json_pointer
return nullptr;
}
// the reference token between the slash at pos and the next one
std::size_t end = path.find_first_of('/', pos + 1);
if (end == string_t::npos || end > length)
// the reference token between the slash at pos and the next one,
// which must not have an escape
const char* const begin = path.data() + pos + 1;
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
{
end = length;
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
{
return nullptr;
}
++end;
}
switch (ptr->type())
@@ -821,7 +822,7 @@ class json_pointer
case detail::value_t::object:
{
token.clear();
token.append(path.data() + pos + 1, end - pos - 1);
token.append(begin, static_cast<std::size_t>(end - begin));
const auto it = ptr->m_data.m_value.object->find(token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{
@@ -833,10 +834,24 @@ class json_pointer
case detail::value_t::array:
{
typename BasicJsonType::size_type idx{};
token.clear();
token.append(path.data() + pos + 1, end - pos - 1);
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
// an index of up to 18 digits without a leading zero fits into size_type
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
const std::size_t digits = static_cast<std::size_t>(end - begin);
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
{
return nullptr;
}
typename BasicJsonType::size_type idx = 0;
for (const char* c = begin; c != end; ++c)
{
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
{
return nullptr;
}
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
}
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
@@ -855,7 +870,7 @@ class json_pointer
default:
return nullptr;
}
pos = end;
pos = static_cast<std::size_t>(end - path.data());
steps.emplace_back(pos, ptr);
}
+31 -16
View File
@@ -23145,12 +23145,6 @@ class json_pointer
}
cache.path = &path;
// the resolved prefix has no "~"
if (JSON_HEDLEY_UNLIKELY(length > steps[depth - 1].first && std::memchr(path.data() + steps[depth - 1].first, '~', length - steps[depth - 1].first) != nullptr))
{
return nullptr;
}
BasicJsonType* ptr = steps[depth - 1].second;
for (std::size_t pos = steps[depth - 1].first; pos < length;)
{
@@ -23159,11 +23153,18 @@ class json_pointer
return nullptr;
}
// the reference token between the slash at pos and the next one
std::size_t end = path.find_first_of('/', pos + 1);
if (end == string_t::npos || end > length)
// the reference token between the slash at pos and the next one,
// which must not have an escape
const char* const begin = path.data() + pos + 1;
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
{
end = length;
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
{
return nullptr;
}
++end;
}
switch (ptr->type())
@@ -23171,7 +23172,7 @@ class json_pointer
case detail::value_t::object:
{
token.clear();
token.append(path.data() + pos + 1, end - pos - 1);
token.append(begin, static_cast<std::size_t>(end - begin));
const auto it = ptr->m_data.m_value.object->find(token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{
@@ -23183,10 +23184,24 @@ class json_pointer
case detail::value_t::array:
{
typename BasicJsonType::size_type idx{};
token.clear();
token.append(path.data() + pos + 1, end - pos - 1);
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
// an index of up to 18 digits without a leading zero fits into size_type
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
const std::size_t digits = static_cast<std::size_t>(end - begin);
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
{
return nullptr;
}
typename BasicJsonType::size_type idx = 0;
for (const char* c = begin; c != end; ++c)
{
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
{
return nullptr;
}
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
}
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
return nullptr;
}
@@ -23205,7 +23220,7 @@ class json_pointer
default:
return nullptr;
}
pos = end;
pos = static_cast<std::size_t>(end - path.data());
steps.emplace_back(pos, ptr);
}