From 2a2c7d30f0dd0483bfbb78b575430c173a516908 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 21:37:06 +0200 Subject: [PATCH] Split a JSON pointer in one scan and construct the tokens in place split() looked for the end of every reference token with find_first_of('/'), for a '~' in it with a second find_first_of, and moved the token into the vector. One loop over the characters now finds both, and a token without an escape is constructed in the vector. Tokens with an escape are checked and unescaped as before, so the exceptions are the same. json_pointer(string) over the keys of citm_catalog (instructions on macOS, Linux Ir GCC 13 / Clang 18): 42.3M -> 36.4M, 42.9M -> 35.1M / 41.2M -> 34.7M; twitter 17.7M -> 16.2M, 18.5M -> 16.3M / 17.6M -> 16.0M. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 57 +++++++++++++----------- single_include/nlohmann/json.hpp | 57 +++++++++++++----------- 2 files changed, 60 insertions(+), 54 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index a382ae6ef..6398a8d22 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -1198,33 +1198,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(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] == '~'); @@ -1238,10 +1237,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; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 89e89c12a..a101e8b97 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -23548,33 +23548,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(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] == '~'); @@ -23588,10 +23587,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;