diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 32cb350d0..50fe7f53b 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -8,7 +8,7 @@ #pragma once -#include // all_of +#include // all_of, copy #include // array #include // isdigit #include // 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(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; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 8c5d8b623..f53ebbbf9 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -22510,7 +22510,7 @@ NLOHMANN_JSON_NAMESPACE_END -#include // all_of +#include // all_of, copy #include // array #include // isdigit #include // errno, ERANGE @@ -22590,14 +22590,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) { @@ -23823,33 +23847,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] == '~'); @@ -23863,10 +23886,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;