mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 14:08:13 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
892a2dd957 | ||
|
|
42bd27824e | ||
|
|
a89d3d2738 | ||
|
|
f930ed5f71 | ||
|
|
7521d0436b | ||
|
|
39ae5bc7be | ||
|
|
ba25f96169 | ||
|
|
f2c0e8b6f9 | ||
|
|
38ba507196 | ||
|
|
bd4760322f |
No files matched your search
@@ -15,7 +15,7 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint64_t
|
#include <cstdint> // uint64_t
|
||||||
#include <cstdlib> // strtoull
|
#include <cstdlib> // strtoull
|
||||||
#include <cstring> // memcmp, memcpy
|
#include <cstring> // memchr, memcmp, memcpy
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
@@ -303,6 +303,12 @@ 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
|
||||||
@@ -328,7 +334,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() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
|
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
|
||||||
{
|
{
|
||||||
return array_index_status::not_a_number;
|
return array_index_status::not_a_number;
|
||||||
}
|
}
|
||||||
@@ -475,9 +481,40 @@ 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 BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||||
|
-> 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);
|
||||||
@@ -488,6 +525,12 @@ 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
|
||||||
{
|
{
|
||||||
@@ -720,48 +763,103 @@ 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] ptr the value the pointer starts at
|
@param[in] root 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: a
|
an existing value, or does so in a way that is not plain: an array
|
||||||
reference token with an escape, an array index that is malformed or
|
index that is malformed or out of range, or a "-", or a reference
|
||||||
out of range, or a "-". No exception is thrown for these; callers
|
token with an escape. No exception is
|
||||||
use @ref json_pointer instead, which handles them.
|
thrown for these; callers use @ref json_pointer instead, which
|
||||||
|
handles them.
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
|
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
|
||||||
{
|
{
|
||||||
for (std::size_t pos = 0; pos < length;)
|
auto& steps = cache.steps;
|
||||||
|
|
||||||
|
// 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,
|
||||||
std::size_t end = path.find_first_of('/', pos + 1);
|
// which must not have an escape
|
||||||
if (end == string_t::npos || end > length)
|
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 == '~'))
|
||||||
}
|
{
|
||||||
token.clear();
|
return nullptr;
|
||||||
token.append(path.data() + pos + 1, end - pos - 1);
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
|
++end;
|
||||||
{
|
|
||||||
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()))
|
||||||
{
|
{
|
||||||
@@ -773,8 +871,24 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
typename BasicJsonType::size_type idx{};
|
// an index of up to 18 digits without a leading zero fits into size_type
|
||||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
|
// (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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -793,7 +907,8 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
pos = end;
|
pos = static_cast<std::size_t>(end - path.data());
|
||||||
|
steps.emplace_back(pos, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptr;
|
return ptr;
|
||||||
@@ -907,7 +1022,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(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(
|
||||||
@@ -978,7 +1093,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(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));
|
||||||
@@ -1040,7 +1155,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -1117,7 +1232,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return false;
|
return false;
|
||||||
@@ -1276,6 +1391,52 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -1385,6 +1546,7 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reserve_flattened(object, value, detail::priority_tag<1> {});
|
||||||
enter(value);
|
enter(value);
|
||||||
while (!stack.empty())
|
while (!stack.empty())
|
||||||
{
|
{
|
||||||
@@ -1392,8 +1554,9 @@ 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;
|
||||||
|
|
||||||
// drop the path of the previous child
|
// the path of the previous child is replaced by the path of the
|
||||||
path.resize(stack.back().path_length);
|
// next one; for an array element in one resize
|
||||||
|
const std::size_t path_length = stack.back().path_length;
|
||||||
|
|
||||||
if (container->is_array())
|
if (container->is_array())
|
||||||
{
|
{
|
||||||
@@ -1407,8 +1570,20 @@ 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;
|
||||||
path.push_back('/');
|
std::array<char, 24> digits{};
|
||||||
append_index(path, i);
|
std::size_t pos = digits.size();
|
||||||
|
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
|
||||||
@@ -1422,6 +1597,7 @@ 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);
|
||||||
@@ -1517,8 +1693,28 @@ 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 = ¤t->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, end - pos - 1);
|
token.append(data + pos + 1, token_size);
|
||||||
if (token.find_first_of('~') != string_t::npos)
|
if (token.find_first_of('~') != string_t::npos)
|
||||||
{
|
{
|
||||||
detail::unescape(token);
|
detail::unescape(token);
|
||||||
@@ -1528,10 +1724,13 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
{
|
{
|
||||||
if (arrays.contains(data, pos))
|
// a first child 0 makes the value an array without a look-up
|
||||||
|
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 = ¤t->operator[](array_index<BasicJsonType>(token));
|
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
+157
-32
@@ -6878,6 +6878,72 @@ 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)
|
||||||
@@ -7051,18 +7117,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;
|
||||||
const string_t value_key = "value";
|
typename json_pointer::template plain_steps<basic_json> steps;
|
||||||
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
|
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
|
||||||
{
|
{
|
||||||
if (path.empty() || path.find_first_of('~') != string_t::npos)
|
if (path.empty())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a missing "value" is reported below
|
// a missing "value" is reported below
|
||||||
const auto value_it = val.m_data.m_value.object->find(value_key);
|
const basic_json* const value_ptr = members.value;
|
||||||
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
|
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
|
||||||
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
|
if (operation != patch_operations::remove && value_ptr == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -7071,7 +7137,7 @@ public:
|
|||||||
{
|
{
|
||||||
case patch_operations::replace:
|
case patch_operations::replace:
|
||||||
{
|
{
|
||||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||||
if (target == nullptr)
|
if (target == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -7082,7 +7148,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::test:
|
case patch_operations::test:
|
||||||
{
|
{
|
||||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||||
// a failing test is reported below
|
// a failing test is reported below
|
||||||
return target != nullptr && *target == value;
|
return target != nullptr && *target == value;
|
||||||
}
|
}
|
||||||
@@ -7102,13 +7168,17 @@ public:
|
|||||||
}
|
}
|
||||||
--slash;
|
--slash;
|
||||||
|
|
||||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
|
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -7164,31 +7234,32 @@ 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](const string_t& op,
|
const auto get_value = [&val](basic_json * found,
|
||||||
const string_t& member,
|
const string_t& op,
|
||||||
|
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(it == val.m_data.m_value.object->end()))
|
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
|
||||||
{
|
{
|
||||||
detail::throw_patch_missing_member(op, member, false, val);
|
detail::throw_patch_missing_member(op, string_t(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 && !it->second.is_string()))
|
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
|
||||||
{
|
{
|
||||||
detail::throw_patch_missing_member(op, member, true, val);
|
detail::throw_patch_missing_member(op, string_t(member), true, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no error: return value
|
// no error: return value
|
||||||
return it->second;
|
return *found;
|
||||||
};
|
};
|
||||||
|
|
||||||
// type check: every element of the array must be an object
|
// type check: every element of the array must be an object
|
||||||
@@ -7197,26 +7268,30 @@ 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("op", "op", true).m_data.m_value.string;
|
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
|
||||||
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
const string_t& path = *get_value(members.path, 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, val))
|
if (try_plain(operation, path, members, 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("add", "value", false));
|
operation_add(ptr, get_value(members.value, "add", "value", false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7229,13 +7304,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("replace", "value", false);
|
result.at(ptr) = get_value(members.value, "replace", "value", false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case patch_operations::move:
|
case patch_operations::move:
|
||||||
{
|
{
|
||||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
const string_t& from_path = *get_value(members.from, "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)))
|
||||||
@@ -7258,7 +7333,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::copy:
|
case patch_operations::copy:
|
||||||
{
|
{
|
||||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
const string_t& from_path = *get_value(members.from, "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"
|
||||||
@@ -7276,7 +7351,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("test", "value", false));
|
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
|
||||||
}
|
}
|
||||||
JSON_INTERNAL_CATCH (out_of_range&)
|
JSON_INTERNAL_CATCH (out_of_range&)
|
||||||
{
|
{
|
||||||
@@ -7368,13 +7443,18 @@ 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);
|
||||||
entry["op"] = op;
|
auto& members = *entry.m_data.m_value.object;
|
||||||
entry["path"] = path;
|
typename object_t::iterator hint = members.end();
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
entry["value"] = *value;
|
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
|
||||||
}
|
}
|
||||||
|
entry.set_parents();
|
||||||
result.push_back(std::move(entry));
|
result.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7396,6 +7476,50 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -7476,8 +7600,9 @@ 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, std::vector<const basic_json*>& target_values,
|
const string_t& path, TargetValues& 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
|
||||||
@@ -7691,7 +7816,7 @@ public:
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
std::vector<const basic_json*> target_values;
|
diff_target_values 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))
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+389
-65
@@ -22357,7 +22357,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint64_t
|
#include <cstdint> // uint64_t
|
||||||
#include <cstdlib> // strtoull
|
#include <cstdlib> // strtoull
|
||||||
#include <cstring> // memcmp, memcpy
|
#include <cstring> // memchr, memcmp, memcpy
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
@@ -22653,6 +22653,12 @@ 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
|
||||||
@@ -22678,7 +22684,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() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
|
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
|
||||||
{
|
{
|
||||||
return array_index_status::not_a_number;
|
return array_index_status::not_a_number;
|
||||||
}
|
}
|
||||||
@@ -22825,9 +22831,40 @@ 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 BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||||
|
-> 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);
|
||||||
@@ -22838,6 +22875,12 @@ 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
|
||||||
{
|
{
|
||||||
@@ -23070,48 +23113,103 @@ 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] ptr the value the pointer starts at
|
@param[in] root 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: a
|
an existing value, or does so in a way that is not plain: an array
|
||||||
reference token with an escape, an array index that is malformed or
|
index that is malformed or out of range, or a "-", or a reference
|
||||||
out of range, or a "-". No exception is thrown for these; callers
|
token with an escape. No exception is
|
||||||
use @ref json_pointer instead, which handles them.
|
thrown for these; callers use @ref json_pointer instead, which
|
||||||
|
handles them.
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
|
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
|
||||||
{
|
{
|
||||||
for (std::size_t pos = 0; pos < length;)
|
auto& steps = cache.steps;
|
||||||
|
|
||||||
|
// 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,
|
||||||
std::size_t end = path.find_first_of('/', pos + 1);
|
// which must not have an escape
|
||||||
if (end == string_t::npos || end > length)
|
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 == '~'))
|
||||||
}
|
{
|
||||||
token.clear();
|
return nullptr;
|
||||||
token.append(path.data() + pos + 1, end - pos - 1);
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
|
++end;
|
||||||
{
|
|
||||||
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()))
|
||||||
{
|
{
|
||||||
@@ -23123,8 +23221,24 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
typename BasicJsonType::size_type idx{};
|
// an index of up to 18 digits without a leading zero fits into size_type
|
||||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
|
// (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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -23143,7 +23257,8 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
pos = end;
|
pos = static_cast<std::size_t>(end - path.data());
|
||||||
|
steps.emplace_back(pos, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptr;
|
return ptr;
|
||||||
@@ -23257,7 +23372,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(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(
|
||||||
@@ -23328,7 +23443,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(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));
|
||||||
@@ -23390,7 +23505,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -23467,7 +23582,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return false;
|
return false;
|
||||||
@@ -23626,6 +23741,52 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -23735,6 +23896,7 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reserve_flattened(object, value, detail::priority_tag<1> {});
|
||||||
enter(value);
|
enter(value);
|
||||||
while (!stack.empty())
|
while (!stack.empty())
|
||||||
{
|
{
|
||||||
@@ -23742,8 +23904,9 @@ 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;
|
||||||
|
|
||||||
// drop the path of the previous child
|
// the path of the previous child is replaced by the path of the
|
||||||
path.resize(stack.back().path_length);
|
// next one; for an array element in one resize
|
||||||
|
const std::size_t path_length = stack.back().path_length;
|
||||||
|
|
||||||
if (container->is_array())
|
if (container->is_array())
|
||||||
{
|
{
|
||||||
@@ -23757,8 +23920,20 @@ 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;
|
||||||
path.push_back('/');
|
std::array<char, 24> digits{};
|
||||||
append_index(path, i);
|
std::size_t pos = digits.size();
|
||||||
|
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
|
||||||
@@ -23772,6 +23947,7 @@ 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);
|
||||||
@@ -23867,8 +24043,28 @@ 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 = ¤t->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, end - pos - 1);
|
token.append(data + pos + 1, token_size);
|
||||||
if (token.find_first_of('~') != string_t::npos)
|
if (token.find_first_of('~') != string_t::npos)
|
||||||
{
|
{
|
||||||
detail::unescape(token);
|
detail::unescape(token);
|
||||||
@@ -23878,10 +24074,13 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
{
|
{
|
||||||
if (arrays.contains(data, pos))
|
// a first child 0 makes the value an array without a look-up
|
||||||
|
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 = ¤t->operator[](array_index<BasicJsonType>(token));
|
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -39188,6 +39387,72 @@ 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)
|
||||||
@@ -39361,18 +39626,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;
|
||||||
const string_t value_key = "value";
|
typename json_pointer::template plain_steps<basic_json> steps;
|
||||||
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
|
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
|
||||||
{
|
{
|
||||||
if (path.empty() || path.find_first_of('~') != string_t::npos)
|
if (path.empty())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a missing "value" is reported below
|
// a missing "value" is reported below
|
||||||
const auto value_it = val.m_data.m_value.object->find(value_key);
|
const basic_json* const value_ptr = members.value;
|
||||||
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
|
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
|
||||||
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
|
if (operation != patch_operations::remove && value_ptr == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -39381,7 +39646,7 @@ public:
|
|||||||
{
|
{
|
||||||
case patch_operations::replace:
|
case patch_operations::replace:
|
||||||
{
|
{
|
||||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||||
if (target == nullptr)
|
if (target == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -39392,7 +39657,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::test:
|
case patch_operations::test:
|
||||||
{
|
{
|
||||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||||
// a failing test is reported below
|
// a failing test is reported below
|
||||||
return target != nullptr && *target == value;
|
return target != nullptr && *target == value;
|
||||||
}
|
}
|
||||||
@@ -39412,13 +39677,17 @@ public:
|
|||||||
}
|
}
|
||||||
--slash;
|
--slash;
|
||||||
|
|
||||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
|
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -39474,31 +39743,32 @@ 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](const string_t& op,
|
const auto get_value = [&val](basic_json * found,
|
||||||
const string_t& member,
|
const string_t& op,
|
||||||
|
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(it == val.m_data.m_value.object->end()))
|
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
|
||||||
{
|
{
|
||||||
detail::throw_patch_missing_member(op, member, false, val);
|
detail::throw_patch_missing_member(op, string_t(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 && !it->second.is_string()))
|
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
|
||||||
{
|
{
|
||||||
detail::throw_patch_missing_member(op, member, true, val);
|
detail::throw_patch_missing_member(op, string_t(member), true, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no error: return value
|
// no error: return value
|
||||||
return it->second;
|
return *found;
|
||||||
};
|
};
|
||||||
|
|
||||||
// type check: every element of the array must be an object
|
// type check: every element of the array must be an object
|
||||||
@@ -39507,26 +39777,30 @@ 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("op", "op", true).m_data.m_value.string;
|
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
|
||||||
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
const string_t& path = *get_value(members.path, 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, val))
|
if (try_plain(operation, path, members, 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("add", "value", false));
|
operation_add(ptr, get_value(members.value, "add", "value", false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39539,13 +39813,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("replace", "value", false);
|
result.at(ptr) = get_value(members.value, "replace", "value", false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case patch_operations::move:
|
case patch_operations::move:
|
||||||
{
|
{
|
||||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
const string_t& from_path = *get_value(members.from, "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)))
|
||||||
@@ -39568,7 +39842,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::copy:
|
case patch_operations::copy:
|
||||||
{
|
{
|
||||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
const string_t& from_path = *get_value(members.from, "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"
|
||||||
@@ -39586,7 +39860,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("test", "value", false));
|
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
|
||||||
}
|
}
|
||||||
JSON_INTERNAL_CATCH (out_of_range&)
|
JSON_INTERNAL_CATCH (out_of_range&)
|
||||||
{
|
{
|
||||||
@@ -39678,13 +39952,18 @@ 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);
|
||||||
entry["op"] = op;
|
auto& members = *entry.m_data.m_value.object;
|
||||||
entry["path"] = path;
|
typename object_t::iterator hint = members.end();
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
entry["value"] = *value;
|
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
|
||||||
}
|
}
|
||||||
|
entry.set_parents();
|
||||||
result.push_back(std::move(entry));
|
result.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39706,6 +39985,50 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -39786,8 +40109,9 @@ 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, std::vector<const basic_json*>& target_values,
|
const string_t& path, TargetValues& 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
|
||||||
@@ -40001,7 +40325,7 @@ public:
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
std::vector<const basic_json*> target_values;
|
diff_target_values 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))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user