Compare commits

..
Author SHA1 Message Date
Niels Lohmann a9ed3feca3 Copy the tokens of json_pointer::to_string() with std::copy
clang-tidy (bugprone-not-null-terminated-result) took the memcpy into
the result string for a C string missing its terminator. std::copy is
the same memmove; to_string() instructions are unchanged (Apple clang).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 13:32:19 +02:00
Niels Lohmann eb2a092d70 Copy the tokens of a pointer without escapes straight into to_string()
to_string() appended a slash and every escaped token with library calls.
Tokens that contain neither '~' nor '/' (nearly all) are copied into a
string of the final size instead; the others are escaped as before.

json_pointer::to_string() over the keys of citm_catalog (instructions on macOS,
Linux Ir GCC 13 / Clang 18): 47.5M -> 30.6M, 33.6M -> 25.8M / 35.9M -> 22.8M;
twitter 18.8M -> 12.9M, 13.7M -> 11.2M / 14.5M -> 9.9M.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:30:56 +02:00
Niels Lohmann 2a2c7d30f0 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 <mail@nlohmann.me>
2026-10-22 09:30:54 +02:00
3 changed files with 236 additions and 834 deletions

No files matched your search

+86 -260
View File
@@ -8,14 +8,14 @@
#pragma once #pragma once
#include <algorithm> // all_of #include <algorithm> // all_of, copy
#include <array> // array #include <array> // array
#include <cctype> // isdigit #include <cctype> // isdigit
#include <cerrno> // errno, ERANGE #include <cerrno> // errno, ERANGE
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint64_t #include <cstdint> // uint64_t
#include <cstdlib> // strtoull #include <cstdlib> // strtoull
#include <cstring> // memchr, memcmp, memcpy #include <cstring> // memcmp, memcpy
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
#include <iosfwd> // ostream #include <iosfwd> // ostream
#endif // JSON_NO_IO #endif // JSON_NO_IO
@@ -82,12 +82,34 @@ class json_pointer
{ {
// one buffer: every token adds its escaped form (at least its length) and a slash // one buffer: every token adds its escaped form (at least its length) and a slash
std::size_t length = 0; std::size_t length = 0;
bool plain = true;
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
length += reference_token.size() + 1; 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; string_t result;
if (JSON_HEDLEY_LIKELY(plain))
{
// nothing to escape: the tokens are copied into the string
result.resize(length);
if (length != 0)
{
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); result.reserve(length);
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
@@ -303,12 +325,6 @@ class json_pointer
exceeds_size_type ///< @a s converts to an integer that exceeds size_type exceeds_size_type ///< @a s converts to an integer that exceeds size_type
}; };
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
static bool is_dash(const string_t& s) noexcept
{
return s.size() == 1 && s[0] == '-';
}
/*! /*!
@param[in] s reference token to be converted into an array index @param[in] s reference token to be converted into an array index
@param[out] idx the integer representation of @a s if @ref array_index_status::ok @param[out] idx the integer representation of @a s if @ref array_index_status::ok
@@ -334,7 +350,7 @@ class json_pointer
// error condition (cf. RFC 6901, Sect. 4); this also covers single- // error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty // character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved // token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9'))) if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
{ {
return array_index_status::not_a_number; return array_index_status::not_a_number;
} }
@@ -481,40 +497,9 @@ class json_pointer
return pos == size; return pos == size;
} }
/// reserve room for @a n elements of an array type that has reserve()
template<typename ArrayType>
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
-> decltype(array.reserve(n), void())
{
array.reserve(n);
}
template<typename ArrayType>
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
{}
/// @brief object member access with a key that is only copied if it is inserted /// @brief object member access with a key that is only copied if it is inserted
///
/// Objects that can find the position of a key and insert there
/// (std::map) search once, others look the key up and add it if it is missing.
template<typename BasicJsonType> template<typename BasicJsonType>
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/) static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
{
auto& members = *object.m_data.m_value.object;
auto it = members.lower_bound(key);
if (it != members.end() && !members.key_comp()(key, it->first))
{
return it->second;
}
const auto old_capacity = object.container_capacity();
it = members.emplace_hint(it, key, nullptr);
return object.set_parent_after_insert(it->second, old_capacity);
}
template<typename BasicJsonType>
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
{ {
auto& members = *object.m_data.m_value.object; auto& members = *object.m_data.m_value.object;
const auto it = members.find(key); const auto it = members.find(key);
@@ -525,12 +510,6 @@ class json_pointer
return object.operator[](key); return object.operator[](key);
} }
template<typename BasicJsonType>
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
return object_member_impl(object, key, detail::priority_tag<1> {});
}
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
json_pointer top() const json_pointer top() const
{ {
@@ -763,103 +742,48 @@ class json_pointer
} }
} }
/*!
@brief the values along the pointer last resolved by @ref resolve_plain
A patch from @ref basic_json::diff lists its operations in document order,
so consecutive pointers share most of their reference tokens; the values
along the previous pointer are kept to resume from the common prefix.
`steps[k]` is the value after the first `k` reference tokens together with
the length of the prefix of `*path` that holds them. A value stays valid as
long as no operation changes its container: callers keep only values up to
the parent of the pointer they modify, or call `reset()`.
*/
template<typename BasicJsonType>
struct plain_steps
{
const string_t* path = nullptr;
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
void reset() noexcept
{
path = nullptr;
steps.clear();
}
};
/*! /*!
@brief resolve a plain JSON pointer without building its reference tokens @brief resolve a plain JSON pointer without building its reference tokens
@param[in] root the value the pointer starts at @param[in] ptr the value the pointer starts at
@param[in] path a JSON pointer string @param[in] path a JSON pointer string
@param[in] length the number of leading bytes of @a path to resolve; 0 or @param[in] length the number of leading bytes of @a path to resolve; 0 or
the position of a slash the position of a slash
@param[in,out] token a string to hold the reference tokens, to reuse its storage @param[in,out] token a string to hold the reference tokens, to reuse its storage
@param[in,out] cache the values along the previously resolved pointer;
@a path must outlive the next call
@return the pointed to value, or `nullptr` if the pointer does not denote @return the pointed to value, or `nullptr` if the pointer does not denote
an existing value, or does so in a way that is not plain: an array an existing value, or does so in a way that is not plain: a
index that is malformed or out of range, or a "-", or a reference reference token with an escape, an array index that is malformed or
token with an escape. No exception is out of range, or a "-". No exception is thrown for these; callers
thrown for these; callers use @ref json_pointer instead, which use @ref json_pointer instead, which handles them.
handles them.
*/ */
template<typename BasicJsonType> template<typename BasicJsonType>
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache) static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
{ {
auto& steps = cache.steps; for (std::size_t pos = 0; pos < length;)
// resume from the deepest step that is also a prefix of this path
std::size_t depth = 1;
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
{
const string_t& previous = *cache.path;
for (depth = steps.size(); depth > 1; --depth)
{
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
{
break;
}
}
steps.resize(depth);
}
else
{
steps.assign(1, std::make_pair(std::size_t{0}, root));
}
cache.path = &path;
BasicJsonType* ptr = steps[depth - 1].second;
for (std::size_t pos = steps[depth - 1].first; pos < length;)
{ {
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/')) if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
{ {
return nullptr; return nullptr;
} }
// the reference token between the slash at pos and the next one, // the reference token between the slash at pos and the next one
// which must not have an escape std::size_t end = path.find_first_of('/', pos + 1);
const char* const begin = path.data() + pos + 1; if (end == string_t::npos || end > length)
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
{ {
if (JSON_HEDLEY_UNLIKELY(*end == '~')) end = length;
{ }
return nullptr; token.clear();
} token.append(path.data() + pos + 1, end - pos - 1);
++end; if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
{
return nullptr;
} }
switch (ptr->type()) switch (ptr->type())
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
token.clear();
token.append(begin, static_cast<std::size_t>(end - begin));
const auto it = ptr->m_data.m_value.object->find(token); const auto it = ptr->m_data.m_value.object->find(token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{ {
@@ -871,24 +795,8 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
// an index of up to 18 digits without a leading zero fits into size_type typename BasicJsonType::size_type idx{};
// (9 digits for a 32-bit size_type); everything else is left to json_pointer if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
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; return nullptr;
} }
@@ -907,8 +815,7 @@ class json_pointer
default: default:
return nullptr; return nullptr;
} }
pos = static_cast<std::size_t>(end - path.data()); pos = end;
steps.emplace_back(pos, ptr);
} }
return ptr; return ptr;
@@ -1022,7 +929,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat( JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
@@ -1093,7 +1000,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" cannot be used for const access // "-" cannot be used for const access
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
@@ -1155,7 +1062,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
return nullptr; return nullptr;
@@ -1232,7 +1139,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
return false; return false;
@@ -1313,33 +1220,32 @@ class json_pointer
} }
result.reserve(token_count); result.reserve(token_count);
// extract the reference tokens: // extract the reference tokens: every one runs from after a slash to
// - slash: position of the last read slash (or end of string) // the next slash or the end of the string; a token without a '~' (the
// - start: position after the previous slash // common case) is stored as it is, others are checked and unescaped
for ( const char* const data = reference_string.data();
// search for the first slash after the first character const char* const end = data + size;
std::size_t slash = reference_string.find_first_of('/', 1), for (const char* start = data + 1;; ++start)
// 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))
{ {
// use the text between the beginning of the reference token const char* slash = start;
// (start) and the last slash (slash). bool escaped = false;
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start; while (slash != end && *slash != '/')
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)
{ {
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] == '~'); JSON_ASSERT(reference_token[pos] == '~');
@@ -1353,10 +1259,14 @@ class json_pointer
} }
detail::unescape(reference_token); detail::unescape(reference_token);
result.push_back(std::move(reference_token));
} }
// finally, store the reference token if (slash == end)
result.push_back(std::move(reference_token)); {
break;
}
start = slash;
} }
return result; return result;
@@ -1391,52 +1301,6 @@ class json_pointer
obj[key] = std::forward<ValueType>(v); obj[key] = std::forward<ValueType>(v);
} }
/// the number of entries that flattening @a value adds: its primitive
/// values and empty containers
template<typename BasicJsonType>
static std::size_t count_flattened(const BasicJsonType& value)
{
std::size_t count = 0;
std::vector<const BasicJsonType*> pending(1, &value);
while (!pending.empty())
{
const BasicJsonType* const v = pending.back();
pending.pop_back();
if (v->is_array() && !v->m_data.m_value.array->empty())
{
for (const auto& element : *v->m_data.m_value.array)
{
pending.push_back(&element);
}
}
else if (v->is_object() && !v->m_data.m_value.object->empty())
{
for (const auto& member : *v->m_data.m_value.object)
{
pending.push_back(&member.second);
}
}
else
{
++count;
}
}
return count;
}
// Reserve the storage for @a value of objects that have a reserve(), so
// that it does not have to be moved while it grows
template<typename ObjectType, typename BasicJsonType>
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
-> decltype(obj.reserve(std::size_t {}), void())
{
obj.reserve(count_flattened(value));
}
template<typename ObjectType, typename BasicJsonType>
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
{}
/// append the decimal representation of @a i to @a out /// append the decimal representation of @a i to @a out
static void append_index(string_t& out, std::size_t i) static void append_index(string_t& out, std::size_t i)
{ {
@@ -1546,7 +1410,6 @@ class json_pointer
} }
}; };
reserve_flattened(object, value, detail::priority_tag<1> {});
enter(value); enter(value);
while (!stack.empty()) while (!stack.empty())
{ {
@@ -1554,9 +1417,8 @@ class json_pointer
// frame, which would invalidate a reference to it // frame, which would invalidate a reference to it
const BasicJsonType* const container = stack.back().container; const BasicJsonType* const container = stack.back().container;
// the path of the previous child is replaced by the path of the // drop the path of the previous child
// next one; for an array element in one resize path.resize(stack.back().path_length);
const std::size_t path_length = stack.back().path_length;
if (container->is_array()) if (container->is_array())
{ {
@@ -1570,20 +1432,8 @@ class json_pointer
// iterate array and use index as a reference string // iterate array and use index as a reference string
++stack.back().index; ++stack.back().index;
std::array<char, 24> digits{}; path.push_back('/');
std::size_t pos = digits.size(); append_index(path, i);
std::size_t rest = i;
do
{
digits[--pos] = static_cast<char>('0' + rest % 10);
rest /= 10;
}
while (rest != 0);
const std::size_t count = digits.size() - pos;
path.resize(path_length + 1 + count);
char* const out = &path[path_length];
out[0] = '/';
std::memcpy(out + 1, digits.data() + pos, count);
enter(array[i]); enter(array[i]);
} }
else else
@@ -1597,7 +1447,6 @@ class json_pointer
// iterate object and use keys as reference string // iterate object and use keys as reference string
++stack.back().member; ++stack.back().member;
path.resize(path_length);
path.push_back('/'); path.push_back('/');
detail::escape_into(path, it->first); detail::escape_into(path, it->first);
enter(it->second); enter(it->second);
@@ -1693,28 +1542,8 @@ class json_pointer
{ {
end = key.size(); end = key.size();
} }
const std::size_t token_size = end - pos - 1;
// a token that is a plain index of up to 9 digits, which is
// the case for almost all array elements, is not copied
std::size_t index = 0;
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
{
plain_index = data[k] >= '0' && data[k] <= '9';
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
}
if (plain_index)
{
current = &current->operator[](static_cast<typename BasicJsonType::size_type>(index));
values.push_back(current);
ends.push_back(end);
pos = end;
continue;
}
token.clear(); token.clear();
token.append(data + pos + 1, token_size); token.append(data + pos + 1, end - pos - 1);
if (token.find_first_of('~') != string_t::npos) if (token.find_first_of('~') != string_t::npos)
{ {
detail::unescape(token); detail::unescape(token);
@@ -1724,13 +1553,10 @@ class json_pointer
{ {
case detail::value_t::null: case detail::value_t::null:
{ {
// a first child 0 makes the value an array without a look-up if (arrays.contains(data, pos))
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
{ {
// some reference token below this position is 0, so the // some reference token below this position is 0, so the
// value is an array // value is an array
*current = BasicJsonType(detail::value_t::array);
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
current = &current->operator[](array_index<BasicJsonType>(token)); current = &current->operator[](array_index<BasicJsonType>(token));
} }
else else
+32 -157
View File
@@ -6878,72 +6878,6 @@ public:
/// @name JSON Patch functions /// @name JSON Patch functions
/// @{ /// @{
private:
/// the members of an operation of a JSON patch that are of interest
struct patch_members
{
basic_json* op = nullptr;
basic_json* path = nullptr;
basic_json* value = nullptr;
basic_json* from = nullptr;
};
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
{
patch_members members;
for (auto& member : *val.m_data.m_value.object)
{
const string_t& key = member.first;
switch (key.size())
{
case 2:
if (key == "op")
{
members.op = &member.second;
}
break;
case 4:
if (key == "path")
{
members.path = &member.second;
}
else if (key == "from")
{
members.from = &member.second;
}
break;
case 5:
if (key == "value")
{
members.value = &member.second;
}
break;
default:
break;
}
}
return members;
}
/// @brief find the members by lookup for key types other than string_t
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
{
const auto find_member = [&val](const char* name) -> basic_json *
{
const auto it = val.m_data.m_value.object->find(string_t(name));
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
};
patch_members members;
members.op = find_member("op");
members.path = find_member("path");
members.value = find_member("value");
members.from = find_member("from");
return members;
}
public:
/// @brief applies a JSON patch in-place without copying the object /// @brief applies a JSON patch in-place without copying the object
/// @sa https://json.nlohmann.me/api/basic_json/patch/ /// @sa https://json.nlohmann.me/api/basic_json/patch/
void patch_inplace(const basic_json& json_patch) void patch_inplace(const basic_json& json_patch)
@@ -7117,18 +7051,18 @@ public:
// a diff creates them: apply the operation and return true; return // a diff creates them: apply the operation and return true; return
// false, with nothing changed, for all others // false, with nothing changed, for all others
string_t token; string_t token;
typename json_pointer::template plain_steps<basic_json> steps; const string_t value_key = "value";
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val) const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
{ {
if (path.empty()) if (path.empty() || path.find_first_of('~') != string_t::npos)
{ {
return false; return false;
} }
// a missing "value" is reported below // a missing "value" is reported below
const basic_json* const value_ptr = members.value; const auto value_it = val.m_data.m_value.object->find(value_key);
const basic_json& value = value_ptr != nullptr ? *value_ptr : val; const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
if (operation != patch_operations::remove && value_ptr == nullptr) if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
{ {
return false; return false;
} }
@@ -7137,7 +7071,7 @@ public:
{ {
case patch_operations::replace: case patch_operations::replace:
{ {
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps); basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
if (target == nullptr) if (target == nullptr)
{ {
return false; return false;
@@ -7148,7 +7082,7 @@ public:
case patch_operations::test: case patch_operations::test:
{ {
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps); const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
// a failing test is reported below // a failing test is reported below
return target != nullptr && *target == value; return target != nullptr && *target == value;
} }
@@ -7168,17 +7102,13 @@ public:
} }
--slash; --slash;
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps); basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
if (parent == nullptr) if (parent == nullptr)
{ {
return false; return false;
} }
token.clear(); token.clear();
token.append(path.data() + slash + 1, path.size() - slash - 1); token.append(path.data() + slash + 1, path.size() - slash - 1);
if (token.find_first_of('~') != string_t::npos)
{
return false;
}
if (operation == patch_operations::add) if (operation == patch_operations::add)
{ {
@@ -7234,32 +7164,31 @@ public:
} }
}; };
// the name of the first member to check
const string_t op_member = "op";
// iterate and apply the operations // iterate and apply the operations
for (const auto& val : json_patch) for (const auto& val : json_patch)
{ {
// wrapper to get a value for an operation // wrapper to get a value for an operation
const auto get_value = [&val](basic_json * found, const auto get_value = [&val](const string_t& op,
const string_t& op, const string_t& member,
const char* member,
bool string_type) -> basic_json & bool string_type) -> basic_json &
{ {
// find value
auto it = val.m_data.m_value.object->find(member);
// check if the desired value is present // check if the desired value is present
if (JSON_HEDLEY_UNLIKELY(found == nullptr)) if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
{ {
detail::throw_patch_missing_member(op, string_t(member), false, val); detail::throw_patch_missing_member(op, member, false, val);
} }
// check if the result is of type string // check if the result is of type string
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string())) if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
{ {
detail::throw_patch_missing_member(op, string_t(member), true, val); detail::throw_patch_missing_member(op, member, true, val);
} }
// no error: return value // no error: return value
return *found; return it->second;
}; };
// type check: every element of the array must be an object // type check: every element of the array must be an object
@@ -7268,30 +7197,26 @@ public:
detail::throw_patch_not_an_array(val); detail::throw_patch_not_an_array(val);
} }
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
// collect mandatory members // collect mandatory members
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string; const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string; const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
const patch_operations operation = get_op(op); const patch_operations operation = get_op(op);
// the usual operations on a plain pointer are applied without // the usual operations on a plain pointer are applied without
// building the pointer's reference tokens; anything else (and // building the pointer's reference tokens; anything else (and
// every error) is handled below // every error) is handled below
if (try_plain(operation, path, members, val)) if (try_plain(operation, path, val))
{ {
continue; continue;
} }
// the general operations may change any container
steps.reset();
json_pointer ptr(path); json_pointer ptr(path);
switch (operation) switch (operation)
{ {
case patch_operations::add: case patch_operations::add:
{ {
operation_add(ptr, get_value(members.value, "add", "value", false)); operation_add(ptr, get_value("add", "value", false));
break; break;
} }
@@ -7304,13 +7229,13 @@ public:
case patch_operations::replace: case patch_operations::replace:
{ {
// the "path" location must exist - use at() // the "path" location must exist - use at()
result.at(ptr) = get_value(members.value, "replace", "value", false); result.at(ptr) = get_value("replace", "value", false);
break; break;
} }
case patch_operations::move: case patch_operations::move:
{ {
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string; const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
json_pointer from_ptr(from_path); json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
@@ -7333,7 +7258,7 @@ public:
case patch_operations::copy: case patch_operations::copy:
{ {
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string; const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
const json_pointer from_ptr(from_path); const json_pointer from_ptr(from_path);
// The copy is functionally identical to an "add" // The copy is functionally identical to an "add"
@@ -7351,7 +7276,7 @@ public:
{ {
// check if "value" matches the one at "path" // check if "value" matches the one at "path"
// the "path" location must exist - use at() // the "path" location must exist - use at()
success = (result.at(ptr) == get_value(members.value, "test", "value", false)); success = (result.at(ptr) == get_value("test", "value", false));
} }
JSON_INTERNAL_CATCH (out_of_range&) JSON_INTERNAL_CATCH (out_of_range&)
{ {
@@ -7443,18 +7368,13 @@ public:
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null /// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value) static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
{ {
// the keys are distinct and in the order of std::map; the members are
// added without looking them up
basic_json entry(value_t::object); basic_json entry(value_t::object);
auto& members = *entry.m_data.m_value.object; entry["op"] = op;
typename object_t::iterator hint = members.end(); entry["path"] = path;
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
if (value != nullptr) if (value != nullptr)
{ {
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {}); entry["value"] = *value;
} }
entry.set_parents();
result.push_back(std::move(entry)); result.push_back(std::move(entry));
} }
@@ -7476,50 +7396,6 @@ public:
diff_operation(result, "add", path, &value); diff_operation(result, "add", path, &value);
} }
/// @brief the values in the target of the members of a source object, for the
/// objects of up to 16 members without allocating
///
/// reserve() must be called with the number of values that are added.
class diff_target_values
{
public:
diff_target_values() = default;
diff_target_values(const diff_target_values&) = delete;
diff_target_values& operator=(const diff_target_values&) = delete;
~diff_target_values() = default;
void reserve(const std::size_t n)
{
if (n > inline_size)
{
large.resize(n);
items = large.data();
}
}
void push_back(const basic_json* value)
{
items[count++] = value;
}
std::size_t size() const noexcept
{
return count;
}
const basic_json* operator[](const std::size_t i) const noexcept
{
return items[i];
}
private:
static constexpr std::size_t inline_size = 16;
std::size_t count = 0;
const basic_json** items = inline_items;
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
const basic_json* inline_items[inline_size]; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
};
/// @brief append "/" and the decimal representation of @a index to @a path /// @brief append "/" and the decimal representation of @a index to @a path
static void diff_append_index(string_t& path, std::size_t index) static void diff_append_index(string_t& path, std::size_t index)
{ {
@@ -7600,9 +7476,8 @@ public:
operations that remove every member of @a source and add every member of operations that remove every member of @a source and add every member of
@a target to @a result, and returns false. @a target to @a result, and returns false.
*/ */
template<typename TargetValues>
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target, static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, TargetValues& target_values, const string_t& path, std::vector<const basic_json*>& target_values,
std::vector<const_iterator>& added) std::vector<const_iterator>& added)
{ {
// first pass: one lookup per source key // first pass: one lookup per source key
@@ -7816,7 +7691,7 @@ public:
case value_t::object: case value_t::object:
{ {
diff_target_values target_values; std::vector<const basic_json*> target_values;
std::vector<const_iterator> added; std::vector<const_iterator> added;
if (diff_object_keys(result, source, target, path, target_values, added)) if (diff_object_keys(result, source, target, path, target_values, added))
{ {
+118 -417
View File
@@ -22350,14 +22350,14 @@ NLOHMANN_JSON_NAMESPACE_END
#include <algorithm> // all_of #include <algorithm> // all_of, copy
#include <array> // array #include <array> // array
#include <cctype> // isdigit #include <cctype> // isdigit
#include <cerrno> // errno, ERANGE #include <cerrno> // errno, ERANGE
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint64_t #include <cstdint> // uint64_t
#include <cstdlib> // strtoull #include <cstdlib> // strtoull
#include <cstring> // memchr, memcmp, memcpy #include <cstring> // memcmp, memcpy
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
#include <iosfwd> // ostream #include <iosfwd> // ostream
#endif // JSON_NO_IO #endif // JSON_NO_IO
@@ -22432,12 +22432,34 @@ class json_pointer
{ {
// one buffer: every token adds its escaped form (at least its length) and a slash // one buffer: every token adds its escaped form (at least its length) and a slash
std::size_t length = 0; std::size_t length = 0;
bool plain = true;
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
length += reference_token.size() + 1; 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; string_t result;
if (JSON_HEDLEY_LIKELY(plain))
{
// nothing to escape: the tokens are copied into the string
result.resize(length);
if (length != 0)
{
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); result.reserve(length);
for (const auto& reference_token : reference_tokens) for (const auto& reference_token : reference_tokens)
{ {
@@ -22653,12 +22675,6 @@ class json_pointer
exceeds_size_type ///< @a s converts to an integer that exceeds size_type exceeds_size_type ///< @a s converts to an integer that exceeds size_type
}; };
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
static bool is_dash(const string_t& s) noexcept
{
return s.size() == 1 && s[0] == '-';
}
/*! /*!
@param[in] s reference token to be converted into an array index @param[in] s reference token to be converted into an array index
@param[out] idx the integer representation of @a s if @ref array_index_status::ok @param[out] idx the integer representation of @a s if @ref array_index_status::ok
@@ -22684,7 +22700,7 @@ class json_pointer
// error condition (cf. RFC 6901, Sect. 4); this also covers single- // error condition (cf. RFC 6901, Sect. 4); this also covers single-
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty // character tokens, so "/x" and "/xy" fail alike; "-" and the empty
// token are left to the conversion below and are reported as unresolved // token are left to the conversion below and are reported as unresolved
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9'))) if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
{ {
return array_index_status::not_a_number; return array_index_status::not_a_number;
} }
@@ -22831,40 +22847,9 @@ class json_pointer
return pos == size; return pos == size;
} }
/// reserve room for @a n elements of an array type that has reserve()
template<typename ArrayType>
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
-> decltype(array.reserve(n), void())
{
array.reserve(n);
}
template<typename ArrayType>
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
{}
/// @brief object member access with a key that is only copied if it is inserted /// @brief object member access with a key that is only copied if it is inserted
///
/// Objects that can find the position of a key and insert there
/// (std::map) search once, others look the key up and add it if it is missing.
template<typename BasicJsonType> template<typename BasicJsonType>
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/) static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
{
auto& members = *object.m_data.m_value.object;
auto it = members.lower_bound(key);
if (it != members.end() && !members.key_comp()(key, it->first))
{
return it->second;
}
const auto old_capacity = object.container_capacity();
it = members.emplace_hint(it, key, nullptr);
return object.set_parent_after_insert(it->second, old_capacity);
}
template<typename BasicJsonType>
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
{ {
auto& members = *object.m_data.m_value.object; auto& members = *object.m_data.m_value.object;
const auto it = members.find(key); const auto it = members.find(key);
@@ -22875,12 +22860,6 @@ class json_pointer
return object.operator[](key); return object.operator[](key);
} }
template<typename BasicJsonType>
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
{
return object_member_impl(object, key, detail::priority_tag<1> {});
}
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
json_pointer top() const json_pointer top() const
{ {
@@ -23113,103 +23092,48 @@ class json_pointer
} }
} }
/*!
@brief the values along the pointer last resolved by @ref resolve_plain
A patch from @ref basic_json::diff lists its operations in document order,
so consecutive pointers share most of their reference tokens; the values
along the previous pointer are kept to resume from the common prefix.
`steps[k]` is the value after the first `k` reference tokens together with
the length of the prefix of `*path` that holds them. A value stays valid as
long as no operation changes its container: callers keep only values up to
the parent of the pointer they modify, or call `reset()`.
*/
template<typename BasicJsonType>
struct plain_steps
{
const string_t* path = nullptr;
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
void reset() noexcept
{
path = nullptr;
steps.clear();
}
};
/*! /*!
@brief resolve a plain JSON pointer without building its reference tokens @brief resolve a plain JSON pointer without building its reference tokens
@param[in] root the value the pointer starts at @param[in] ptr the value the pointer starts at
@param[in] path a JSON pointer string @param[in] path a JSON pointer string
@param[in] length the number of leading bytes of @a path to resolve; 0 or @param[in] length the number of leading bytes of @a path to resolve; 0 or
the position of a slash the position of a slash
@param[in,out] token a string to hold the reference tokens, to reuse its storage @param[in,out] token a string to hold the reference tokens, to reuse its storage
@param[in,out] cache the values along the previously resolved pointer;
@a path must outlive the next call
@return the pointed to value, or `nullptr` if the pointer does not denote @return the pointed to value, or `nullptr` if the pointer does not denote
an existing value, or does so in a way that is not plain: an array an existing value, or does so in a way that is not plain: a
index that is malformed or out of range, or a "-", or a reference reference token with an escape, an array index that is malformed or
token with an escape. No exception is out of range, or a "-". No exception is thrown for these; callers
thrown for these; callers use @ref json_pointer instead, which use @ref json_pointer instead, which handles them.
handles them.
*/ */
template<typename BasicJsonType> template<typename BasicJsonType>
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache) static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
{ {
auto& steps = cache.steps; for (std::size_t pos = 0; pos < length;)
// resume from the deepest step that is also a prefix of this path
std::size_t depth = 1;
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
{
const string_t& previous = *cache.path;
for (depth = steps.size(); depth > 1; --depth)
{
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
{
break;
}
}
steps.resize(depth);
}
else
{
steps.assign(1, std::make_pair(std::size_t{0}, root));
}
cache.path = &path;
BasicJsonType* ptr = steps[depth - 1].second;
for (std::size_t pos = steps[depth - 1].first; pos < length;)
{ {
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/')) if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
{ {
return nullptr; return nullptr;
} }
// the reference token between the slash at pos and the next one, // the reference token between the slash at pos and the next one
// which must not have an escape std::size_t end = path.find_first_of('/', pos + 1);
const char* const begin = path.data() + pos + 1; if (end == string_t::npos || end > length)
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
{ {
if (JSON_HEDLEY_UNLIKELY(*end == '~')) end = length;
{ }
return nullptr; token.clear();
} token.append(path.data() + pos + 1, end - pos - 1);
++end; if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
{
return nullptr;
} }
switch (ptr->type()) switch (ptr->type())
{ {
case detail::value_t::object: case detail::value_t::object:
{ {
token.clear();
token.append(begin, static_cast<std::size_t>(end - begin));
const auto it = ptr->m_data.m_value.object->find(token); const auto it = ptr->m_data.m_value.object->find(token);
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
{ {
@@ -23221,24 +23145,8 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
// an index of up to 18 digits without a leading zero fits into size_type typename BasicJsonType::size_type idx{};
// (9 digits for a 32-bit size_type); everything else is left to json_pointer if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
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; return nullptr;
} }
@@ -23257,8 +23165,7 @@ class json_pointer
default: default:
return nullptr; return nullptr;
} }
pos = static_cast<std::size_t>(end - path.data()); pos = end;
steps.emplace_back(pos, ptr);
} }
return ptr; return ptr;
@@ -23372,7 +23279,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat( JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
@@ -23443,7 +23350,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" cannot be used for const access // "-" cannot be used for const access
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
@@ -23505,7 +23412,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
return nullptr; return nullptr;
@@ -23582,7 +23489,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
return false; return false;
@@ -23663,33 +23570,32 @@ class json_pointer
} }
result.reserve(token_count); result.reserve(token_count);
// extract the reference tokens: // extract the reference tokens: every one runs from after a slash to
// - slash: position of the last read slash (or end of string) // the next slash or the end of the string; a token without a '~' (the
// - start: position after the previous slash // common case) is stored as it is, others are checked and unescaped
for ( const char* const data = reference_string.data();
// search for the first slash after the first character const char* const end = data + size;
std::size_t slash = reference_string.find_first_of('/', 1), for (const char* start = data + 1;; ++start)
// 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))
{ {
// use the text between the beginning of the reference token const char* slash = start;
// (start) and the last slash (slash). bool escaped = false;
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start; while (slash != end && *slash != '/')
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)
{ {
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] == '~'); JSON_ASSERT(reference_token[pos] == '~');
@@ -23703,10 +23609,14 @@ class json_pointer
} }
detail::unescape(reference_token); detail::unescape(reference_token);
result.push_back(std::move(reference_token));
} }
// finally, store the reference token if (slash == end)
result.push_back(std::move(reference_token)); {
break;
}
start = slash;
} }
return result; return result;
@@ -23741,52 +23651,6 @@ class json_pointer
obj[key] = std::forward<ValueType>(v); obj[key] = std::forward<ValueType>(v);
} }
/// the number of entries that flattening @a value adds: its primitive
/// values and empty containers
template<typename BasicJsonType>
static std::size_t count_flattened(const BasicJsonType& value)
{
std::size_t count = 0;
std::vector<const BasicJsonType*> pending(1, &value);
while (!pending.empty())
{
const BasicJsonType* const v = pending.back();
pending.pop_back();
if (v->is_array() && !v->m_data.m_value.array->empty())
{
for (const auto& element : *v->m_data.m_value.array)
{
pending.push_back(&element);
}
}
else if (v->is_object() && !v->m_data.m_value.object->empty())
{
for (const auto& member : *v->m_data.m_value.object)
{
pending.push_back(&member.second);
}
}
else
{
++count;
}
}
return count;
}
// Reserve the storage for @a value of objects that have a reserve(), so
// that it does not have to be moved while it grows
template<typename ObjectType, typename BasicJsonType>
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
-> decltype(obj.reserve(std::size_t {}), void())
{
obj.reserve(count_flattened(value));
}
template<typename ObjectType, typename BasicJsonType>
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
{}
/// append the decimal representation of @a i to @a out /// append the decimal representation of @a i to @a out
static void append_index(string_t& out, std::size_t i) static void append_index(string_t& out, std::size_t i)
{ {
@@ -23896,7 +23760,6 @@ class json_pointer
} }
}; };
reserve_flattened(object, value, detail::priority_tag<1> {});
enter(value); enter(value);
while (!stack.empty()) while (!stack.empty())
{ {
@@ -23904,9 +23767,8 @@ class json_pointer
// frame, which would invalidate a reference to it // frame, which would invalidate a reference to it
const BasicJsonType* const container = stack.back().container; const BasicJsonType* const container = stack.back().container;
// the path of the previous child is replaced by the path of the // drop the path of the previous child
// next one; for an array element in one resize path.resize(stack.back().path_length);
const std::size_t path_length = stack.back().path_length;
if (container->is_array()) if (container->is_array())
{ {
@@ -23920,20 +23782,8 @@ class json_pointer
// iterate array and use index as a reference string // iterate array and use index as a reference string
++stack.back().index; ++stack.back().index;
std::array<char, 24> digits{}; path.push_back('/');
std::size_t pos = digits.size(); append_index(path, i);
std::size_t rest = i;
do
{
digits[--pos] = static_cast<char>('0' + rest % 10);
rest /= 10;
}
while (rest != 0);
const std::size_t count = digits.size() - pos;
path.resize(path_length + 1 + count);
char* const out = &path[path_length];
out[0] = '/';
std::memcpy(out + 1, digits.data() + pos, count);
enter(array[i]); enter(array[i]);
} }
else else
@@ -23947,7 +23797,6 @@ class json_pointer
// iterate object and use keys as reference string // iterate object and use keys as reference string
++stack.back().member; ++stack.back().member;
path.resize(path_length);
path.push_back('/'); path.push_back('/');
detail::escape_into(path, it->first); detail::escape_into(path, it->first);
enter(it->second); enter(it->second);
@@ -24043,28 +23892,8 @@ class json_pointer
{ {
end = key.size(); end = key.size();
} }
const std::size_t token_size = end - pos - 1;
// a token that is a plain index of up to 9 digits, which is
// the case for almost all array elements, is not copied
std::size_t index = 0;
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
{
plain_index = data[k] >= '0' && data[k] <= '9';
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
}
if (plain_index)
{
current = &current->operator[](static_cast<typename BasicJsonType::size_type>(index));
values.push_back(current);
ends.push_back(end);
pos = end;
continue;
}
token.clear(); token.clear();
token.append(data + pos + 1, token_size); token.append(data + pos + 1, end - pos - 1);
if (token.find_first_of('~') != string_t::npos) if (token.find_first_of('~') != string_t::npos)
{ {
detail::unescape(token); detail::unescape(token);
@@ -24074,13 +23903,10 @@ class json_pointer
{ {
case detail::value_t::null: case detail::value_t::null:
{ {
// a first child 0 makes the value an array without a look-up if (arrays.contains(data, pos))
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
{ {
// some reference token below this position is 0, so the // some reference token below this position is 0, so the
// value is an array // value is an array
*current = BasicJsonType(detail::value_t::array);
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
current = &current->operator[](array_index<BasicJsonType>(token)); current = &current->operator[](array_index<BasicJsonType>(token));
} }
else else
@@ -39387,72 +39213,6 @@ public:
/// @name JSON Patch functions /// @name JSON Patch functions
/// @{ /// @{
private:
/// the members of an operation of a JSON patch that are of interest
struct patch_members
{
basic_json* op = nullptr;
basic_json* path = nullptr;
basic_json* value = nullptr;
basic_json* from = nullptr;
};
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
{
patch_members members;
for (auto& member : *val.m_data.m_value.object)
{
const string_t& key = member.first;
switch (key.size())
{
case 2:
if (key == "op")
{
members.op = &member.second;
}
break;
case 4:
if (key == "path")
{
members.path = &member.second;
}
else if (key == "from")
{
members.from = &member.second;
}
break;
case 5:
if (key == "value")
{
members.value = &member.second;
}
break;
default:
break;
}
}
return members;
}
/// @brief find the members by lookup for key types other than string_t
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
{
const auto find_member = [&val](const char* name) -> basic_json *
{
const auto it = val.m_data.m_value.object->find(string_t(name));
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
};
patch_members members;
members.op = find_member("op");
members.path = find_member("path");
members.value = find_member("value");
members.from = find_member("from");
return members;
}
public:
/// @brief applies a JSON patch in-place without copying the object /// @brief applies a JSON patch in-place without copying the object
/// @sa https://json.nlohmann.me/api/basic_json/patch/ /// @sa https://json.nlohmann.me/api/basic_json/patch/
void patch_inplace(const basic_json& json_patch) void patch_inplace(const basic_json& json_patch)
@@ -39626,18 +39386,18 @@ public:
// a diff creates them: apply the operation and return true; return // a diff creates them: apply the operation and return true; return
// false, with nothing changed, for all others // false, with nothing changed, for all others
string_t token; string_t token;
typename json_pointer::template plain_steps<basic_json> steps; const string_t value_key = "value";
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val) const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
{ {
if (path.empty()) if (path.empty() || path.find_first_of('~') != string_t::npos)
{ {
return false; return false;
} }
// a missing "value" is reported below // a missing "value" is reported below
const basic_json* const value_ptr = members.value; const auto value_it = val.m_data.m_value.object->find(value_key);
const basic_json& value = value_ptr != nullptr ? *value_ptr : val; const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
if (operation != patch_operations::remove && value_ptr == nullptr) if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
{ {
return false; return false;
} }
@@ -39646,7 +39406,7 @@ public:
{ {
case patch_operations::replace: case patch_operations::replace:
{ {
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps); basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
if (target == nullptr) if (target == nullptr)
{ {
return false; return false;
@@ -39657,7 +39417,7 @@ public:
case patch_operations::test: case patch_operations::test:
{ {
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps); const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
// a failing test is reported below // a failing test is reported below
return target != nullptr && *target == value; return target != nullptr && *target == value;
} }
@@ -39677,17 +39437,13 @@ public:
} }
--slash; --slash;
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps); basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
if (parent == nullptr) if (parent == nullptr)
{ {
return false; return false;
} }
token.clear(); token.clear();
token.append(path.data() + slash + 1, path.size() - slash - 1); token.append(path.data() + slash + 1, path.size() - slash - 1);
if (token.find_first_of('~') != string_t::npos)
{
return false;
}
if (operation == patch_operations::add) if (operation == patch_operations::add)
{ {
@@ -39743,32 +39499,31 @@ public:
} }
}; };
// the name of the first member to check
const string_t op_member = "op";
// iterate and apply the operations // iterate and apply the operations
for (const auto& val : json_patch) for (const auto& val : json_patch)
{ {
// wrapper to get a value for an operation // wrapper to get a value for an operation
const auto get_value = [&val](basic_json * found, const auto get_value = [&val](const string_t& op,
const string_t& op, const string_t& member,
const char* member,
bool string_type) -> basic_json & bool string_type) -> basic_json &
{ {
// find value
auto it = val.m_data.m_value.object->find(member);
// check if the desired value is present // check if the desired value is present
if (JSON_HEDLEY_UNLIKELY(found == nullptr)) if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
{ {
detail::throw_patch_missing_member(op, string_t(member), false, val); detail::throw_patch_missing_member(op, member, false, val);
} }
// check if the result is of type string // check if the result is of type string
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string())) if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
{ {
detail::throw_patch_missing_member(op, string_t(member), true, val); detail::throw_patch_missing_member(op, member, true, val);
} }
// no error: return value // no error: return value
return *found; return it->second;
}; };
// type check: every element of the array must be an object // type check: every element of the array must be an object
@@ -39777,30 +39532,26 @@ public:
detail::throw_patch_not_an_array(val); detail::throw_patch_not_an_array(val);
} }
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
// collect mandatory members // collect mandatory members
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string; const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string; const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
const patch_operations operation = get_op(op); const patch_operations operation = get_op(op);
// the usual operations on a plain pointer are applied without // the usual operations on a plain pointer are applied without
// building the pointer's reference tokens; anything else (and // building the pointer's reference tokens; anything else (and
// every error) is handled below // every error) is handled below
if (try_plain(operation, path, members, val)) if (try_plain(operation, path, val))
{ {
continue; continue;
} }
// the general operations may change any container
steps.reset();
json_pointer ptr(path); json_pointer ptr(path);
switch (operation) switch (operation)
{ {
case patch_operations::add: case patch_operations::add:
{ {
operation_add(ptr, get_value(members.value, "add", "value", false)); operation_add(ptr, get_value("add", "value", false));
break; break;
} }
@@ -39813,13 +39564,13 @@ public:
case patch_operations::replace: case patch_operations::replace:
{ {
// the "path" location must exist - use at() // the "path" location must exist - use at()
result.at(ptr) = get_value(members.value, "replace", "value", false); result.at(ptr) = get_value("replace", "value", false);
break; break;
} }
case patch_operations::move: case patch_operations::move:
{ {
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string; const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
json_pointer from_ptr(from_path); json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
@@ -39842,7 +39593,7 @@ public:
case patch_operations::copy: case patch_operations::copy:
{ {
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string; const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
const json_pointer from_ptr(from_path); const json_pointer from_ptr(from_path);
// The copy is functionally identical to an "add" // The copy is functionally identical to an "add"
@@ -39860,7 +39611,7 @@ public:
{ {
// check if "value" matches the one at "path" // check if "value" matches the one at "path"
// the "path" location must exist - use at() // the "path" location must exist - use at()
success = (result.at(ptr) == get_value(members.value, "test", "value", false)); success = (result.at(ptr) == get_value("test", "value", false));
} }
JSON_INTERNAL_CATCH (out_of_range&) JSON_INTERNAL_CATCH (out_of_range&)
{ {
@@ -39952,18 +39703,13 @@ public:
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null /// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value) static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
{ {
// the keys are distinct and in the order of std::map; the members are
// added without looking them up
basic_json entry(value_t::object); basic_json entry(value_t::object);
auto& members = *entry.m_data.m_value.object; entry["op"] = op;
typename object_t::iterator hint = members.end(); entry["path"] = path;
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
if (value != nullptr) if (value != nullptr)
{ {
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {}); entry["value"] = *value;
} }
entry.set_parents();
result.push_back(std::move(entry)); result.push_back(std::move(entry));
} }
@@ -39985,50 +39731,6 @@ public:
diff_operation(result, "add", path, &value); diff_operation(result, "add", path, &value);
} }
/// @brief the values in the target of the members of a source object, for the
/// objects of up to 16 members without allocating
///
/// reserve() must be called with the number of values that are added.
class diff_target_values
{
public:
diff_target_values() = default;
diff_target_values(const diff_target_values&) = delete;
diff_target_values& operator=(const diff_target_values&) = delete;
~diff_target_values() = default;
void reserve(const std::size_t n)
{
if (n > inline_size)
{
large.resize(n);
items = large.data();
}
}
void push_back(const basic_json* value)
{
items[count++] = value;
}
std::size_t size() const noexcept
{
return count;
}
const basic_json* operator[](const std::size_t i) const noexcept
{
return items[i];
}
private:
static constexpr std::size_t inline_size = 16;
std::size_t count = 0;
const basic_json** items = inline_items;
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
const basic_json* inline_items[inline_size]; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
};
/// @brief append "/" and the decimal representation of @a index to @a path /// @brief append "/" and the decimal representation of @a index to @a path
static void diff_append_index(string_t& path, std::size_t index) static void diff_append_index(string_t& path, std::size_t index)
{ {
@@ -40109,9 +39811,8 @@ public:
operations that remove every member of @a source and add every member of operations that remove every member of @a source and add every member of
@a target to @a result, and returns false. @a target to @a result, and returns false.
*/ */
template<typename TargetValues>
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target, static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, TargetValues& target_values, const string_t& path, std::vector<const basic_json*>& target_values,
std::vector<const_iterator>& added) std::vector<const_iterator>& added)
{ {
// first pass: one lookup per source key // first pass: one lookup per source key
@@ -40325,7 +40026,7 @@ public:
case value_t::object: case value_t::object:
{ {
diff_target_values target_values; std::vector<const basic_json*> target_values;
std::vector<const_iterator> added; std::vector<const_iterator> added;
if (diff_object_keys(result, source, target, path, target_values, added)) if (diff_object_keys(result, source, target, path, target_values, added))
{ {