Split and print JSON pointers in one pass (#5887)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-10-23 06:53:15 +02:00
1 parent 6fa14d8f95
commit 0b924f5375
2 files changed
+110 -56

No files matched your search

+55 -28
View File
@@ -8,7 +8,7 @@
#pragma once
#include <algorithm> // all_of
#include <algorithm> // all_of, copy
#include <array> // array
#include <cctype> // isdigit
#include <cerrno> // errno, ERANGE
@@ -80,14 +80,38 @@ class json_pointer
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
string_t to_string() const
{
if (reference_tokens.empty())
{
return string_t();
}
// one buffer: every token adds its escaped form (at least its length) and a slash
std::size_t length = 0;
bool plain = true;
for (const auto& reference_token : reference_tokens)
{
length += reference_token.size() + 1;
const char* const data = reference_token.data();
for (std::size_t i = 0, size = reference_token.size(); plain && i < size; ++i)
{
plain = data[i] != '~' && data[i] != '/';
}
}
string_t result;
if (JSON_HEDLEY_LIKELY(plain))
{
// nothing to escape: the tokens are copied into the string
result.resize(length);
char* out = &result[0];
for (const auto& reference_token : reference_tokens)
{
*out++ = '/';
out = std::copy(reference_token.data(), reference_token.data() + reference_token.size(), out);
}
return result;
}
result.reserve(length);
for (const auto& reference_token : reference_tokens)
{
@@ -1313,33 +1337,32 @@ class json_pointer
}
result.reserve(token_count);
// extract the reference tokens:
// - slash: position of the last read slash (or end of string)
// - start: position after the previous slash
for (
// search for the first slash after the first character
std::size_t slash = reference_string.find_first_of('/', 1),
// set the beginning of the first reference token
start = 1;
// we can stop if start == 0 (if slash == string_t::npos)
start != 0;
// set the beginning of the next reference token
// (will eventually be 0 if slash == string_t::npos)
start = (slash == string_t::npos) ? 0 : slash + 1,
// find next slash
slash = reference_string.find_first_of('/', start))
// extract the reference tokens: every one runs from after a slash to
// the next slash or the end of the string; a token without a '~' (the
// common case) is stored as it is, others are checked and unescaped
const char* const data = reference_string.data();
const char* const end = data + size;
for (const char* start = data + 1;; ++start)
{
// use the text between the beginning of the reference token
// (start) and the last slash (slash).
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
auto reference_token = string_t(reference_string.data() + start, count);
// check reference tokens are properly escaped; a token without
// a '~' (the common case) needs neither check nor unescaping
std::size_t pos = reference_token.find_first_of('~');
if (pos != string_t::npos)
const char* slash = start;
bool escaped = false;
while (slash != end && *slash != '/')
{
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
escaped = escaped || *slash == '~';
++slash;
}
const auto count = static_cast<std::size_t>(slash - start);
if (JSON_HEDLEY_LIKELY(!escaped))
{
result.emplace_back(start, count);
}
else
{
auto reference_token = string_t(start, count);
// check reference tokens are properly escaped
for (std::size_t pos = reference_token.find_first_of('~'); pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
{
JSON_ASSERT(reference_token[pos] == '~');
@@ -1353,10 +1376,14 @@ class json_pointer
}
detail::unescape(reference_token);
result.push_back(std::move(reference_token));
}
// finally, store the reference token
result.push_back(std::move(reference_token));
if (slash == end)
{
break;
}
start = slash;
}
return result;