Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e051c3bfb | ||
|
|
693f7a1df1 | ||
|
|
ad31938c71 | ||
|
|
908038c027 |
No files matched your search
@@ -674,16 +674,19 @@ class json_sax_dom_callback_parser
|
|||||||
if (keep && ref_stack.back())
|
if (keep && ref_stack.back())
|
||||||
{
|
{
|
||||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||||
const auto it = obj.find(val);
|
// a single lookup: insert the placeholder, or find the existing member
|
||||||
if (it != obj.end())
|
const auto inserted = obj.emplace(val, discarded);
|
||||||
|
const auto it = inserted.first;
|
||||||
|
if (!inserted.second)
|
||||||
{
|
{
|
||||||
// this is a duplicate key (legal in JSON); remember its
|
// this is a duplicate key (legal in JSON); remember its
|
||||||
// current value so it can be restored later if the new
|
// current value so it can be restored later if the new
|
||||||
// value is rejected by the callback, instead of being
|
// value is rejected by the callback, instead of being
|
||||||
// erased together with the discarded placeholder
|
// erased together with the discarded placeholder
|
||||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||||
|
it->second = discarded;
|
||||||
}
|
}
|
||||||
object_element = &(obj[val] = discarded);
|
object_element = &(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
#include <limits> // max
|
#include <limits> // max
|
||||||
#include <map> // map
|
|
||||||
#include <numeric> // accumulate
|
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
@@ -69,12 +67,21 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||||
string_t to_string() const
|
string_t to_string() const
|
||||||
{
|
{
|
||||||
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
// one buffer: every token adds its escaped form (at least its length) and a slash
|
||||||
string_t{},
|
std::size_t length = 0;
|
||||||
[](const string_t& a, const string_t& b)
|
for (const auto& reference_token : reference_tokens)
|
||||||
{
|
{
|
||||||
return detail::concat<string_t>(a, '/', detail::escape(b));
|
length += reference_token.size() + 1;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
string_t result;
|
||||||
|
result.reserve(length);
|
||||||
|
for (const auto& reference_token : reference_tokens)
|
||||||
|
{
|
||||||
|
result.append("/", 1);
|
||||||
|
detail::escape_into(result, reference_token);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief return a string representation of the JSON pointer
|
/// @brief return a string representation of the JSON pointer
|
||||||
@@ -129,21 +136,29 @@ class json_pointer
|
|||||||
friend json_pointer operator/(const json_pointer& lhs,
|
friend json_pointer operator/(const json_pointer& lhs,
|
||||||
const json_pointer& rhs)
|
const json_pointer& rhs)
|
||||||
{
|
{
|
||||||
return json_pointer(lhs) /= rhs;
|
json_pointer result;
|
||||||
|
result.reference_tokens.reserve(lhs.reference_tokens.size() + rhs.reference_tokens.size());
|
||||||
|
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||||
|
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||||
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
||||||
{
|
{
|
||||||
return json_pointer(lhs) /= std::move(token);
|
json_pointer result;
|
||||||
|
result.reference_tokens.reserve(lhs.reference_tokens.size() + 1);
|
||||||
|
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||||
|
result.reference_tokens.push_back(std::move(token));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||||
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
||||||
{
|
{
|
||||||
return json_pointer(lhs) /= array_idx;
|
return lhs / detail::to_string<string_t>(array_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief returns the parent of this JSON pointer
|
/// @brief returns the parent of this JSON pointer
|
||||||
@@ -155,8 +170,8 @@ class json_pointer
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
json_pointer res = *this;
|
json_pointer res;
|
||||||
res.pop_back();
|
res.reference_tokens = std::vector<string_t>(reference_tokens.begin(), reference_tokens.end() - 1);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,14 +260,14 @@ class json_pointer
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/// @throw out_of_range.405 always
|
/// @throw out_of_range.405 always
|
||||||
JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
|
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @throw out_of_range.404 always
|
/// @throw out_of_range.404 always
|
||||||
template<typename BasicJsonContext>
|
template<typename BasicJsonContext>
|
||||||
JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
|
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
|
||||||
static_cast<void>(context);
|
static_cast<void>(context);
|
||||||
@@ -305,6 +320,28 @@ class json_pointer
|
|||||||
return array_index_status::not_a_number;
|
return array_index_status::not_a_number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
|
||||||
|
// size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed
|
||||||
|
constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9;
|
||||||
|
if (!s.empty() && s.size() <= max_fast_digits)
|
||||||
|
{
|
||||||
|
size_type acc = 0;
|
||||||
|
for (std::size_t i = 0; i < s.size(); ++i)
|
||||||
|
{
|
||||||
|
const char c = s[i];
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
|
||||||
|
{
|
||||||
|
break; // not a plain number: let strtoull decide
|
||||||
|
}
|
||||||
|
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||||
|
{
|
||||||
|
idx = acc;
|
||||||
|
return array_index_status::ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* p = s.data();
|
const char* p = s.data();
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
errno = 0; // strtoull doesn't reset errno
|
errno = 0; // strtoull doesn't reset errno
|
||||||
@@ -361,6 +398,83 @@ class json_pointer
|
|||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief compare with a JSON pointer string without splitting it into tokens
|
||||||
|
|
||||||
|
@throw parse_error.107 if the string is not empty or begins with '/'
|
||||||
|
@throw parse_error.108 if character '~' is not followed by '0' or '1'
|
||||||
|
*/
|
||||||
|
bool equals_string(const string_t& reference_string) const
|
||||||
|
{
|
||||||
|
if (reference_string.empty())
|
||||||
|
{
|
||||||
|
return reference_tokens.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||||
|
{
|
||||||
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// the whole string is validated, whether or not it matches
|
||||||
|
const std::size_t size = reference_string.size();
|
||||||
|
for (std::size_t pos = reference_string.find_first_of('~');
|
||||||
|
pos != string_t::npos;
|
||||||
|
pos = reference_string.find_first_of('~', pos + 1))
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1')))
|
||||||
|
{
|
||||||
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare token by token, unescaping the string on the fly
|
||||||
|
std::size_t pos = 0; // the slash in front of the next token
|
||||||
|
for (const auto& reference_token : reference_tokens)
|
||||||
|
{
|
||||||
|
if (pos >= size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t i = pos + 1;
|
||||||
|
std::size_t j = 0;
|
||||||
|
const std::size_t token_size = reference_token.size();
|
||||||
|
while (i < size && reference_string[i] != '/')
|
||||||
|
{
|
||||||
|
char c = reference_string[i++];
|
||||||
|
if (c == '~')
|
||||||
|
{
|
||||||
|
c = (reference_string[i++] == '0') ? '~' : '/';
|
||||||
|
}
|
||||||
|
if (j == token_size || reference_token[j] != c)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
if (j != token_size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pos = i;
|
||||||
|
}
|
||||||
|
return pos == size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief object member access with a key that is only copied if it is inserted
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||||
|
{
|
||||||
|
auto& members = *object.m_data.m_value.object;
|
||||||
|
const auto it = members.find(key);
|
||||||
|
if (it != members.end())
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return object.operator[](key);
|
||||||
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
json_pointer top() const
|
json_pointer top() const
|
||||||
{
|
{
|
||||||
@@ -384,84 +498,141 @@ class json_pointer
|
|||||||
the result independent of the order in which the flattened object is
|
the result independent of the order in which the flattened object is
|
||||||
iterated, which is unspecified for some object types.
|
iterated, which is unspecified for some object types.
|
||||||
|
|
||||||
The prefixes form a tree and are numbered, so each of them is stored only
|
The prefixes form a tree and are numbered (number 0 is the empty prefix),
|
||||||
once (as a node) rather than as a copy of all of its reference tokens.
|
so each of them is stored only once (as a node) rather than as a copy of
|
||||||
|
all of its reference tokens. The tree does not own the reference tokens:
|
||||||
|
it refers to the tokens of the parsed keys, which must outlive it.
|
||||||
*/
|
*/
|
||||||
struct prefix_tree
|
struct prefix_tree
|
||||||
{
|
{
|
||||||
// children[id] maps a reference token to the number of the prefix
|
// open-addressing hash table from (parent, last reference token) to
|
||||||
// extended by that token; number 0 is the empty prefix
|
// the number of the node; empty slots have token == nullptr
|
||||||
std::vector<std::map<string_t, std::size_t>> children;
|
struct slot
|
||||||
|
{
|
||||||
|
std::size_t hash;
|
||||||
|
std::size_t parent;
|
||||||
|
const string_t* token;
|
||||||
|
std::size_t id;
|
||||||
|
|
||||||
|
slot() noexcept
|
||||||
|
: hash(0), parent(0), token(nullptr), id(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept
|
||||||
|
: hash(h), parent(p), token(t), id(i)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<slot> slots;
|
||||||
|
std::size_t used = 0;
|
||||||
|
|
||||||
|
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||||
|
{
|
||||||
|
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
||||||
|
for (std::size_t i = 0; i < token.size(); ++i)
|
||||||
|
{
|
||||||
|
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
|
||||||
|
}
|
||||||
|
return h ^ (h >> 29u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void grow()
|
||||||
|
{
|
||||||
|
std::vector<slot> old(slots.size() * 2);
|
||||||
|
old.swap(slots);
|
||||||
|
const std::size_t mask = slots.size() - 1;
|
||||||
|
for (const auto& s : old)
|
||||||
|
{
|
||||||
|
if (s.token != nullptr)
|
||||||
|
{
|
||||||
|
std::size_t i = s.hash & mask;
|
||||||
|
while (slots[i].token != nullptr)
|
||||||
|
{
|
||||||
|
i = (i + 1) & mask;
|
||||||
|
}
|
||||||
|
slots[i] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent[id] is the number of the prefix without its last token
|
||||||
|
std::vector<std::size_t> parent;
|
||||||
// is_array[id] is true iff some flattened key has the reference token
|
// is_array[id] is true iff some flattened key has the reference token
|
||||||
// 0 directly below the prefix with number id
|
// 0 directly below the prefix with number id
|
||||||
std::vector<bool> is_array;
|
std::vector<bool> is_array;
|
||||||
|
|
||||||
// start with the empty prefix only
|
// start with the empty prefix only
|
||||||
prefix_tree()
|
prefix_tree()
|
||||||
: children(1)
|
: slots(16)
|
||||||
|
, parent(1, 0)
|
||||||
, is_array(1, false)
|
, is_array(1, false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// return the number of the prefix with number id extended by
|
// return the number of the prefix with number id extended by
|
||||||
// reference_token, adding it if it is new
|
// reference_token, adding it if it is new
|
||||||
std::size_t add_child(std::size_t id, string_t&& reference_token)
|
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||||
{
|
{
|
||||||
|
const std::size_t hash = hash_of(id, reference_token);
|
||||||
|
const std::size_t mask = slots.size() - 1;
|
||||||
|
std::size_t i = hash & mask;
|
||||||
|
while (slots[i].token != nullptr)
|
||||||
|
{
|
||||||
|
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
||||||
|
{
|
||||||
|
return slots[i].id;
|
||||||
|
}
|
||||||
|
i = (i + 1) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new prefix
|
||||||
|
const std::size_t child = parent.size();
|
||||||
|
slots[i] = slot(hash, id, &reference_token, child);
|
||||||
if (reference_token == "0")
|
if (reference_token == "0")
|
||||||
{
|
{
|
||||||
is_array[id] = true;
|
is_array[id] = true;
|
||||||
}
|
}
|
||||||
|
parent.push_back(id);
|
||||||
|
is_array.push_back(false);
|
||||||
|
|
||||||
// read the number before the emplace_back below, which may
|
// keep the load factor below 1/2
|
||||||
// reallocate children and invalidate the iterator
|
if (++used * 2 > slots.size())
|
||||||
const std::size_t next = children.size();
|
|
||||||
const auto inserted = children[id].emplace(std::move(reference_token), next);
|
|
||||||
const std::size_t child = inserted.first->second;
|
|
||||||
if (inserted.second)
|
|
||||||
{
|
{
|
||||||
children.emplace_back();
|
grow();
|
||||||
is_array.push_back(false);
|
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the number of the prefix with number id extended by
|
|
||||||
// reference_token, which must have been added before
|
|
||||||
std::size_t find_child(std::size_t id, const string_t& reference_token) const
|
|
||||||
{
|
|
||||||
const auto it = children[id].find(reference_token);
|
|
||||||
JSON_ASSERT(it != children[id].end());
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief create and return a reference to the pointed to value
|
@brief create and return a reference to the pointed to value
|
||||||
|
|
||||||
Complexity: Linear in the number of reference tokens (times the logarithm
|
@param[in] j the value to create the pointed to value in
|
||||||
of the number of siblings for the prefix lookup).
|
@param[in] tokens the reference tokens of the pointer
|
||||||
|
@param[in] prefixes prefixes[i] is the number in @a tree of the prefix
|
||||||
|
consisting of the first i reference tokens
|
||||||
|
|
||||||
|
Complexity: Linear in the number of reference tokens.
|
||||||
|
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@throw parse_error.106 if an array index begins with '0'
|
||||||
@throw parse_error.109 if array index is not a number
|
@throw parse_error.109 if array index is not a number
|
||||||
@throw type_error.313 if value cannot be unflattened
|
@throw type_error.313 if value cannot be unflattened
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
|
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens,
|
||||||
|
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
|
||||||
{
|
{
|
||||||
auto* result = &j;
|
auto* result = &j;
|
||||||
|
|
||||||
// the number of the prefix consumed so far; used to look up whether
|
|
||||||
// the value to be created below is an array or an object
|
|
||||||
std::size_t id = 0;
|
|
||||||
|
|
||||||
// in case no reference tokens exist, return a reference to the JSON value
|
// in case no reference tokens exist, return a reference to the JSON value
|
||||||
// j which will be overwritten by a primitive value
|
// j which will be overwritten by a primitive value
|
||||||
for (const auto& reference_token : reference_tokens)
|
for (std::size_t i = 0; i < tokens.size(); ++i)
|
||||||
{
|
{
|
||||||
|
const auto& reference_token = tokens[i];
|
||||||
switch (result->type())
|
switch (result->type())
|
||||||
{
|
{
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
{
|
{
|
||||||
if (tree.is_array[id])
|
if (tree.is_array[prefixes[i]])
|
||||||
{
|
{
|
||||||
// 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
|
||||||
@@ -478,7 +649,7 @@ class json_pointer
|
|||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
// create an entry in the object
|
// create an entry in the object
|
||||||
result = &result->operator[](reference_token);
|
result = &object_member(*result, reference_token);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,8 +676,6 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
|
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
id = tree.find_child(id, reference_token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return *result;
|
return *result;
|
||||||
@@ -558,7 +727,7 @@ class json_pointer
|
|||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
// use unchecked object access
|
// use unchecked object access
|
||||||
ptr = &ptr->operator[](reference_token);
|
ptr = &object_member(*ptr, reference_token);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,8 +777,13 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
// note: at performs range check
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
ptr = &ptr->at(reference_token);
|
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||||
|
{
|
||||||
|
// at() throws out_of_range.403 with the usual message
|
||||||
|
ptr->at(reference_token);
|
||||||
|
}
|
||||||
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,7 +804,7 @@ class json_pointer
|
|||||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
||||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||||
}
|
}
|
||||||
ptr = &ptr->operator[](idx);
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,7 +852,9 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
// use unchecked object access; the const operator[]
|
// use unchecked object access; the const operator[]
|
||||||
// asserts that the key exists
|
// asserts that the key exists
|
||||||
ptr = &ptr->operator[](reference_token);
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
|
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
|
||||||
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +868,9 @@ class json_pointer
|
|||||||
|
|
||||||
// use unchecked array access; the const operator[]
|
// use unchecked array access; the const operator[]
|
||||||
// asserts that the index exists
|
// asserts that the index exists
|
||||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||||
|
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
|
||||||
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,12 +911,12 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
const auto it = ptr->find(reference_token);
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
ptr = &*it;
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,7 +953,7 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
ptr = &ptr->operator[](idx);
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,13 +986,14 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
if (!ptr->contains(reference_token))
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
|
if (it == ptr->m_data.m_value.object->end())
|
||||||
{
|
{
|
||||||
// we did not find the key in the object
|
// we did not find the key in the object
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = &ptr->operator[](reference_token);
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,13 +1015,13 @@ class json_pointer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->m_data.m_value.array->size())
|
||||||
{
|
{
|
||||||
// index out of range
|
// index out of range
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = &ptr->operator[](idx);
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,6 +1071,15 @@ class json_pointer
|
|||||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// every slash starts a reference token
|
||||||
|
const std::size_t size = reference_string.size();
|
||||||
|
std::size_t token_count = 0;
|
||||||
|
for (std::size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
token_count += (reference_string[i] == '/') ? 1 : 0;
|
||||||
|
}
|
||||||
|
result.reserve(token_count);
|
||||||
|
|
||||||
// extract the reference tokens:
|
// extract the reference tokens:
|
||||||
// - slash: position of the last read slash (or end of string)
|
// - slash: position of the last read slash (or end of string)
|
||||||
// - start: position after the previous slash
|
// - start: position after the previous slash
|
||||||
@@ -913,31 +1101,52 @@ class json_pointer
|
|||||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||||
auto reference_token = string_t(reference_string.data() + start, count);
|
auto reference_token = string_t(reference_string.data() + start, count);
|
||||||
|
|
||||||
// check reference tokens are properly escaped
|
// check reference tokens are properly escaped; a token without
|
||||||
for (std::size_t pos = reference_token.find_first_of('~');
|
// a '~' (the common case) needs neither check nor unescaping
|
||||||
pos != string_t::npos;
|
std::size_t pos = reference_token.find_first_of('~');
|
||||||
pos = reference_token.find_first_of('~', pos + 1))
|
if (pos != string_t::npos)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(reference_token[pos] == '~');
|
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||||
|
|
||||||
// ~ must be followed by 0 or 1
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
|
||||||
(reference_token[pos + 1] != '0' &&
|
|
||||||
reference_token[pos + 1] != '1')))
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
JSON_ASSERT(reference_token[pos] == '~');
|
||||||
|
|
||||||
|
// ~ must be followed by 0 or 1
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
||||||
|
(reference_token[pos + 1] != '0' &&
|
||||||
|
reference_token[pos + 1] != '1')))
|
||||||
|
{
|
||||||
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detail::unescape(reference_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally, store the reference token
|
// finally, store the reference token
|
||||||
detail::unescape(reference_token);
|
result.push_back(std::move(reference_token));
|
||||||
result.push_back(reference_token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise
|
||||||
|
// distinct, so objects that can append a key known to be absent
|
||||||
|
// (ordered_map) skip the lookup, which is linear and would make flatten
|
||||||
|
// quadratic; all other object types use operator[].
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, int /*unused*/)
|
||||||
|
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
|
||||||
|
{
|
||||||
|
obj.emplace_new_key(key, std::forward<ValueType>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, ...)
|
||||||
|
{
|
||||||
|
obj[key] = std::forward<ValueType>(v);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] reference_string the reference string to the current value
|
@param[in] reference_string the reference string to the current value
|
||||||
@param[in] value the value to consider
|
@param[in] value the value to consider
|
||||||
@@ -982,7 +1191,8 @@ class json_pointer
|
|||||||
|
|
||||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||||
// are added to the result right away; other containers get a frame
|
// are added to the result right away; other containers get a frame
|
||||||
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
auto& object = *result.m_data.m_value.object;
|
||||||
|
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
|
||||||
{
|
{
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
@@ -991,7 +1201,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.array->empty())
|
if (v.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
// flatten empty array as null
|
// flatten empty array as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1005,7 +1215,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.object->empty())
|
if (v.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
// flatten empty object as null
|
// flatten empty object as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1025,7 +1235,7 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// add a primitive value with its reference string
|
// add a primitive value with its reference string
|
||||||
result[path] = v;
|
add_flattened(object, path, v, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1071,6 +1281,10 @@ class json_pointer
|
|||||||
enter(it->second);
|
enter(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the entries were added without going through basic_json, so the
|
||||||
|
// parent pointers (JSON_DIAGNOSTICS) have to be set now
|
||||||
|
result.set_parents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1094,21 +1308,53 @@ class json_pointer
|
|||||||
|
|
||||||
BasicJsonType result;
|
BasicJsonType result;
|
||||||
|
|
||||||
|
// parse every key once; the reference tokens are kept for the second
|
||||||
|
// pass (the vectors are never resized, so the tree may refer to them)
|
||||||
|
std::vector<std::vector<string_t>> keys;
|
||||||
|
keys.reserve(value.m_data.m_value.object->size());
|
||||||
|
for (const auto& element : *value.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
json_pointer ptr(element.first);
|
||||||
|
keys.push_back(std::move(ptr.reference_tokens));
|
||||||
|
}
|
||||||
|
|
||||||
// collect the pointer prefixes that have a reference token 0 among
|
// collect the pointer prefixes that have a reference token 0 among
|
||||||
// their children; the values below them are arrays, all others are
|
// their children; the values below them are arrays, all others are
|
||||||
// objects (see prefix_tree)
|
// objects (see prefix_tree)
|
||||||
prefix_tree tree;
|
prefix_tree tree;
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
// the number of the prefix with i + 1 tokens of the previous key; keys
|
||||||
|
// are often sorted, so most of the prefix is the same as for the
|
||||||
|
// previous key and need not be looked up again
|
||||||
|
std::vector<std::size_t> path;
|
||||||
|
const std::vector<string_t>* previous = nullptr;
|
||||||
|
// the number of the complete prefix (i.e., the key) of each key
|
||||||
|
std::vector<std::size_t> leaves;
|
||||||
|
leaves.reserve(keys.size());
|
||||||
|
for (const auto& tokens : keys)
|
||||||
{
|
{
|
||||||
json_pointer ptr(element.first);
|
std::size_t common = 0;
|
||||||
std::size_t id = 0;
|
if (previous != nullptr)
|
||||||
for (auto& reference_token : ptr.reference_tokens)
|
|
||||||
{
|
{
|
||||||
id = tree.add_child(id, std::move(reference_token));
|
const std::size_t limit = (std::min)(tokens.size(), previous->size());
|
||||||
|
while (common < limit && tokens[common] == (*previous)[common])
|
||||||
|
{
|
||||||
|
++common;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
path.resize(common);
|
||||||
|
std::size_t id = common == 0 ? 0 : path[common - 1];
|
||||||
|
for (std::size_t i = common; i < tokens.size(); ++i)
|
||||||
|
{
|
||||||
|
id = tree.add_child(id, tokens[i]);
|
||||||
|
path.push_back(id);
|
||||||
|
}
|
||||||
|
leaves.push_back(id);
|
||||||
|
previous = &tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate the JSON object values
|
// iterate the JSON object values
|
||||||
|
std::vector<std::size_t> prefixes;
|
||||||
|
std::size_t key_index = 0;
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
for (const auto& element : *value.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
||||||
@@ -1116,11 +1362,21 @@ class json_pointer
|
|||||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
|
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& tokens = keys[key_index];
|
||||||
|
// number the prefixes of this key from its last one to the first
|
||||||
|
prefixes.resize(tokens.size());
|
||||||
|
for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i)
|
||||||
|
{
|
||||||
|
id = tree.parent[id];
|
||||||
|
prefixes[i - 1] = id;
|
||||||
|
}
|
||||||
|
++key_index;
|
||||||
|
|
||||||
// Assign the value to the reference pointed to by JSON pointer. Note
|
// Assign the value to the reference pointed to by JSON pointer. Note
|
||||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||||
// function get_and_create returns a reference to the result itself.
|
// function get_and_create returns a reference to the result itself.
|
||||||
// An assignment will then create a primitive value.
|
// An assignment will then create a primitive value.
|
||||||
json_pointer(element.first).get_and_create(result, tree) = element.second;
|
get_and_create(result, tokens, prefixes, tree) = element.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -1159,7 +1415,7 @@ class json_pointer
|
|||||||
= delete;
|
= delete;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
return *this == json_pointer(rhs);
|
return equals_string(rhs);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1243,7 +1499,7 @@ inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|||||||
= delete;
|
= delete;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
return lhs.equals_string(rhs);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1256,7 +1512,7 @@ inline bool operator==(const StringType& lhs,
|
|||||||
= delete;
|
= delete;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
return rhs.equals_string(lhs);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <utility> // move
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
|
||||||
@@ -24,39 +25,46 @@ namespace detail
|
|||||||
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
|
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
|
||||||
*
|
*
|
||||||
* The string is rebuilt in a single pass, appending whole runs between the
|
* The string is rebuilt in a single pass, appending whole runs between the
|
||||||
* characters that need escaping. Scanning with find_first_of() keeps the
|
* characters that need escaping, while repeated replace() calls would move
|
||||||
* common case -- nothing to escape -- as fast as a single search, while
|
* the tail of the string once per escaped character. @ref escape_into appends
|
||||||
* repeated replace() calls would move the tail of the string once per
|
* the escaped string to an existing one.
|
||||||
* escaped character.
|
|
||||||
*/
|
*/
|
||||||
|
template<typename StringType>
|
||||||
|
inline void escape_into(StringType& out, const StringType& s)
|
||||||
|
{
|
||||||
|
const std::size_t size = s.size();
|
||||||
|
std::size_t run = 0;
|
||||||
|
for (std::size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
const char c = s[i];
|
||||||
|
if (c == '~' || c == '/')
|
||||||
|
{
|
||||||
|
out.append(s.data() + run, i - run);
|
||||||
|
out.append(c == '~' ? "~0" : "~1", 2);
|
||||||
|
run = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.append(s.data() + run, size - run);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
inline StringType escape(const StringType& s)
|
inline StringType escape(const StringType& s)
|
||||||
{
|
{
|
||||||
auto next_special = [&s](std::size_t from)
|
// a single scan for both special characters; nothing to escape is the common case
|
||||||
|
const std::size_t size = s.size();
|
||||||
|
std::size_t i = 0;
|
||||||
|
while (i < size && s[i] != '~' && s[i] != '/')
|
||||||
{
|
{
|
||||||
const auto tilde = s.find_first_of('~', from);
|
++i;
|
||||||
const auto slash = s.find_first_of('/', from);
|
}
|
||||||
return tilde < slash ? tilde : slash; // npos is the largest value
|
if (i == size)
|
||||||
};
|
|
||||||
|
|
||||||
auto pos = next_special(0);
|
|
||||||
if (pos == StringType::npos)
|
|
||||||
{
|
{
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringType result;
|
StringType result;
|
||||||
result.reserve(s.size() + 2);
|
result.reserve(size + 2);
|
||||||
|
escape_into(result, s);
|
||||||
std::size_t run = 0;
|
|
||||||
while (pos != StringType::npos)
|
|
||||||
{
|
|
||||||
result.append(s.data() + run, pos - run);
|
|
||||||
result.append(s[pos] == '~' ? "~0" : "~1", 2);
|
|
||||||
run = pos + 1;
|
|
||||||
pos = next_special(run);
|
|
||||||
}
|
|
||||||
result.append(s.data() + run, s.size() - run);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +109,7 @@ inline void unescape(StringType& s)
|
|||||||
pos = s.find_first_of('~', run);
|
pos = s.find_first_of('~', run);
|
||||||
}
|
}
|
||||||
result.append(s.data() + run, s.size() - run);
|
result.append(s.data() + run, s.size() - run);
|
||||||
s = result;
|
s = std::move(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
+20
-26
@@ -6562,24 +6562,18 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// wrapper for "add" operation; add value at ptr
|
// wrapper for "add" operation; add value at ptr
|
||||||
const auto operation_add = [&result](json_pointer & ptr, const basic_json & val)
|
const auto operation_add = [&result](json_pointer & ptr, basic_json val)
|
||||||
{
|
{
|
||||||
// adding to the root of the target document means replacing it
|
// adding to the root of the target document means replacing it
|
||||||
if (ptr.empty())
|
if (ptr.empty())
|
||||||
{
|
{
|
||||||
result = val;
|
result = std::move(val);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the top element of the pointer exists
|
// get reference to the parent of the JSON pointer ptr; a missing
|
||||||
json_pointer const top_pointer = ptr.top();
|
// parent (or one of its ancestors) is reported by at()
|
||||||
if (top_pointer != ptr)
|
auto last_path = std::move(ptr.reference_tokens.back());
|
||||||
{
|
|
||||||
result.at(top_pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get reference to the parent of the JSON pointer ptr
|
|
||||||
const auto last_path = ptr.back();
|
|
||||||
ptr.pop_back();
|
ptr.pop_back();
|
||||||
// parent must exist when performing patch add per RFC6902 specs
|
// parent must exist when performing patch add per RFC6902 specs
|
||||||
basic_json& parent = result.at(ptr);
|
basic_json& parent = result.at(ptr);
|
||||||
@@ -6590,7 +6584,7 @@ public:
|
|||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
// use operator[] to add value
|
// use operator[] to add value
|
||||||
parent[last_path] = val;
|
parent[std::move(last_path)] = std::move(val);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6599,7 +6593,7 @@ public:
|
|||||||
if (last_path == "-")
|
if (last_path == "-")
|
||||||
{
|
{
|
||||||
// special case: append to back
|
// special case: append to back
|
||||||
parent.push_back(val);
|
parent.push_back(std::move(val));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -6611,7 +6605,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default case: insert add offset
|
// default case: insert add offset
|
||||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
|
parent.insert(parent.begin() + static_cast<difference_type>(idx), std::move(val));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -6634,7 +6628,8 @@ public:
|
|||||||
const auto operation_remove = [this, & result](json_pointer & ptr)
|
const auto operation_remove = [this, & result](json_pointer & ptr)
|
||||||
{
|
{
|
||||||
// get reference to the parent of the JSON pointer ptr
|
// get reference to the parent of the JSON pointer ptr
|
||||||
const auto last_path = ptr.back();
|
static_cast<void>(ptr.back()); // throws out_of_range.405 if ptr is the root
|
||||||
|
const auto last_path = std::move(ptr.reference_tokens.back());
|
||||||
ptr.pop_back();
|
ptr.pop_back();
|
||||||
basic_json& parent = result.at(ptr);
|
basic_json& parent = result.at(ptr);
|
||||||
|
|
||||||
@@ -6734,8 +6729,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// collect mandatory members
|
// collect mandatory members
|
||||||
const auto op = get_value("op", "op", true).template get<string_t>();
|
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
|
||||||
const auto path = get_value(op, "path", true).template get<string_t>();
|
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
||||||
json_pointer ptr(path);
|
json_pointer ptr(path);
|
||||||
|
|
||||||
switch (get_op(op))
|
switch (get_op(op))
|
||||||
@@ -6761,7 +6756,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::move:
|
case patch_operations::move:
|
||||||
{
|
{
|
||||||
const auto from_path = get_value("move", "from", true).template get<string_t>();
|
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)))
|
||||||
@@ -6770,29 +6765,28 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the "from" location must exist - use at()
|
// the "from" location must exist - use at()
|
||||||
basic_json const v = result.at(from_ptr);
|
// a root "from" is rejected by operation_remove below, which must leave result intact
|
||||||
|
basic_json v = from_ptr.empty() ? basic_json(result) : std::move(result.at(from_ptr));
|
||||||
|
|
||||||
// The move operation is functionally identical to a
|
// The move operation is functionally identical to a
|
||||||
// "remove" operation on the "from" location, followed
|
// "remove" operation on the "from" location, followed
|
||||||
// immediately by an "add" operation at the target
|
// immediately by an "add" operation at the target
|
||||||
// location with the value that was just removed.
|
// location with the value that was just removed.
|
||||||
operation_remove(from_ptr);
|
operation_remove(from_ptr);
|
||||||
operation_add(ptr, v);
|
operation_add(ptr, std::move(v));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case patch_operations::copy:
|
case patch_operations::copy:
|
||||||
{
|
{
|
||||||
const auto from_path = get_value("copy", "from", true).template get<string_t>();
|
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 "from" location must exist - use at()
|
|
||||||
basic_json const v = result.at(from_ptr);
|
|
||||||
|
|
||||||
// The copy is functionally identical to an "add"
|
// The copy is functionally identical to an "add"
|
||||||
// operation at the target location using the value
|
// operation at the target location using the value
|
||||||
// specified in the "from" member.
|
// specified in the "from" member; the "from" location
|
||||||
operation_add(ptr, v);
|
// must exist - use at()
|
||||||
|
operation_add(ptr, result.at(from_ptr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,17 @@ public:
|
|||||||
return {std::prev(this->end()), true};
|
return {std::prev(this->end()), true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief add an element at the end without checking whether the key exists
|
||||||
|
/// @note The caller must guarantee that @a key is not yet contained, otherwise
|
||||||
|
/// the container holds duplicate keys. This avoids the linear search of
|
||||||
|
/// emplace() and operator[] when many keys known to be new are added.
|
||||||
|
template<class V, detail::enable_if_t<
|
||||||
|
detail::is_constructible<T, V>::value, int> = 0>
|
||||||
|
void emplace_new_key(const key_type& key, V && t)
|
||||||
|
{
|
||||||
|
append(key, std::forward<V>(t));
|
||||||
|
}
|
||||||
|
|
||||||
T& operator[](const key_type& key)
|
T& operator[](const key_type& key)
|
||||||
{
|
{
|
||||||
return emplace(key, T{}).first->second;
|
return emplace(key, T{}).first->second;
|
||||||
|
|||||||
Generated
+415
-143
@@ -3415,6 +3415,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <utility> // move
|
||||||
|
|
||||||
// #include <nlohmann/detail/abi_macros.hpp>
|
// #include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
|
||||||
@@ -3431,39 +3432,46 @@ namespace detail
|
|||||||
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
|
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
|
||||||
*
|
*
|
||||||
* The string is rebuilt in a single pass, appending whole runs between the
|
* The string is rebuilt in a single pass, appending whole runs between the
|
||||||
* characters that need escaping. Scanning with find_first_of() keeps the
|
* characters that need escaping, while repeated replace() calls would move
|
||||||
* common case -- nothing to escape -- as fast as a single search, while
|
* the tail of the string once per escaped character. @ref escape_into appends
|
||||||
* repeated replace() calls would move the tail of the string once per
|
* the escaped string to an existing one.
|
||||||
* escaped character.
|
|
||||||
*/
|
*/
|
||||||
|
template<typename StringType>
|
||||||
|
inline void escape_into(StringType& out, const StringType& s)
|
||||||
|
{
|
||||||
|
const std::size_t size = s.size();
|
||||||
|
std::size_t run = 0;
|
||||||
|
for (std::size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
const char c = s[i];
|
||||||
|
if (c == '~' || c == '/')
|
||||||
|
{
|
||||||
|
out.append(s.data() + run, i - run);
|
||||||
|
out.append(c == '~' ? "~0" : "~1", 2);
|
||||||
|
run = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.append(s.data() + run, size - run);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
inline StringType escape(const StringType& s)
|
inline StringType escape(const StringType& s)
|
||||||
{
|
{
|
||||||
auto next_special = [&s](std::size_t from)
|
// a single scan for both special characters; nothing to escape is the common case
|
||||||
|
const std::size_t size = s.size();
|
||||||
|
std::size_t i = 0;
|
||||||
|
while (i < size && s[i] != '~' && s[i] != '/')
|
||||||
{
|
{
|
||||||
const auto tilde = s.find_first_of('~', from);
|
++i;
|
||||||
const auto slash = s.find_first_of('/', from);
|
}
|
||||||
return tilde < slash ? tilde : slash; // npos is the largest value
|
if (i == size)
|
||||||
};
|
|
||||||
|
|
||||||
auto pos = next_special(0);
|
|
||||||
if (pos == StringType::npos)
|
|
||||||
{
|
{
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringType result;
|
StringType result;
|
||||||
result.reserve(s.size() + 2);
|
result.reserve(size + 2);
|
||||||
|
escape_into(result, s);
|
||||||
std::size_t run = 0;
|
|
||||||
while (pos != StringType::npos)
|
|
||||||
{
|
|
||||||
result.append(s.data() + run, pos - run);
|
|
||||||
result.append(s[pos] == '~' ? "~0" : "~1", 2);
|
|
||||||
run = pos + 1;
|
|
||||||
pos = next_special(run);
|
|
||||||
}
|
|
||||||
result.append(s.data() + run, s.size() - run);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3508,7 +3516,7 @@ inline void unescape(StringType& s)
|
|||||||
pos = s.find_first_of('~', run);
|
pos = s.find_first_of('~', run);
|
||||||
}
|
}
|
||||||
result.append(s.data() + run, s.size() - run);
|
result.append(s.data() + run, s.size() - run);
|
||||||
s = result;
|
s = std::move(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
@@ -13850,16 +13858,19 @@ class json_sax_dom_callback_parser
|
|||||||
if (keep && ref_stack.back())
|
if (keep && ref_stack.back())
|
||||||
{
|
{
|
||||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||||
const auto it = obj.find(val);
|
// a single lookup: insert the placeholder, or find the existing member
|
||||||
if (it != obj.end())
|
const auto inserted = obj.emplace(val, discarded);
|
||||||
|
const auto it = inserted.first;
|
||||||
|
if (!inserted.second)
|
||||||
{
|
{
|
||||||
// this is a duplicate key (legal in JSON); remember its
|
// this is a duplicate key (legal in JSON); remember its
|
||||||
// current value so it can be restored later if the new
|
// current value so it can be restored later if the new
|
||||||
// value is rejected by the callback, instead of being
|
// value is rejected by the callback, instead of being
|
||||||
// erased together with the discarded placeholder
|
// erased together with the discarded placeholder
|
||||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||||
|
it->second = discarded;
|
||||||
}
|
}
|
||||||
object_element = &(obj[val] = discarded);
|
object_element = &(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -20631,8 +20642,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
#include <limits> // max
|
#include <limits> // max
|
||||||
#include <map> // map
|
|
||||||
#include <numeric> // accumulate
|
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
@@ -20690,12 +20699,21 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||||
string_t to_string() const
|
string_t to_string() const
|
||||||
{
|
{
|
||||||
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
// one buffer: every token adds its escaped form (at least its length) and a slash
|
||||||
string_t{},
|
std::size_t length = 0;
|
||||||
[](const string_t& a, const string_t& b)
|
for (const auto& reference_token : reference_tokens)
|
||||||
{
|
{
|
||||||
return detail::concat<string_t>(a, '/', detail::escape(b));
|
length += reference_token.size() + 1;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
string_t result;
|
||||||
|
result.reserve(length);
|
||||||
|
for (const auto& reference_token : reference_tokens)
|
||||||
|
{
|
||||||
|
result.append("/", 1);
|
||||||
|
detail::escape_into(result, reference_token);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief return a string representation of the JSON pointer
|
/// @brief return a string representation of the JSON pointer
|
||||||
@@ -20750,21 +20768,29 @@ class json_pointer
|
|||||||
friend json_pointer operator/(const json_pointer& lhs,
|
friend json_pointer operator/(const json_pointer& lhs,
|
||||||
const json_pointer& rhs)
|
const json_pointer& rhs)
|
||||||
{
|
{
|
||||||
return json_pointer(lhs) /= rhs;
|
json_pointer result;
|
||||||
|
result.reference_tokens.reserve(lhs.reference_tokens.size() + rhs.reference_tokens.size());
|
||||||
|
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||||
|
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||||
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
||||||
{
|
{
|
||||||
return json_pointer(lhs) /= std::move(token);
|
json_pointer result;
|
||||||
|
result.reference_tokens.reserve(lhs.reference_tokens.size() + 1);
|
||||||
|
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||||
|
result.reference_tokens.push_back(std::move(token));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||||
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
||||||
{
|
{
|
||||||
return json_pointer(lhs) /= array_idx;
|
return lhs / detail::to_string<string_t>(array_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief returns the parent of this JSON pointer
|
/// @brief returns the parent of this JSON pointer
|
||||||
@@ -20776,8 +20802,8 @@ class json_pointer
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
json_pointer res = *this;
|
json_pointer res;
|
||||||
res.pop_back();
|
res.reference_tokens = std::vector<string_t>(reference_tokens.begin(), reference_tokens.end() - 1);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20866,14 +20892,14 @@ class json_pointer
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/// @throw out_of_range.405 always
|
/// @throw out_of_range.405 always
|
||||||
JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
|
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @throw out_of_range.404 always
|
/// @throw out_of_range.404 always
|
||||||
template<typename BasicJsonContext>
|
template<typename BasicJsonContext>
|
||||||
JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
|
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
|
||||||
static_cast<void>(context);
|
static_cast<void>(context);
|
||||||
@@ -20926,6 +20952,28 @@ class json_pointer
|
|||||||
return array_index_status::not_a_number;
|
return array_index_status::not_a_number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
|
||||||
|
// size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed
|
||||||
|
constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9;
|
||||||
|
if (!s.empty() && s.size() <= max_fast_digits)
|
||||||
|
{
|
||||||
|
size_type acc = 0;
|
||||||
|
for (std::size_t i = 0; i < s.size(); ++i)
|
||||||
|
{
|
||||||
|
const char c = s[i];
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
|
||||||
|
{
|
||||||
|
break; // not a plain number: let strtoull decide
|
||||||
|
}
|
||||||
|
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||||
|
{
|
||||||
|
idx = acc;
|
||||||
|
return array_index_status::ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* p = s.data();
|
const char* p = s.data();
|
||||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
errno = 0; // strtoull doesn't reset errno
|
errno = 0; // strtoull doesn't reset errno
|
||||||
@@ -20982,6 +21030,83 @@ class json_pointer
|
|||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief compare with a JSON pointer string without splitting it into tokens
|
||||||
|
|
||||||
|
@throw parse_error.107 if the string is not empty or begins with '/'
|
||||||
|
@throw parse_error.108 if character '~' is not followed by '0' or '1'
|
||||||
|
*/
|
||||||
|
bool equals_string(const string_t& reference_string) const
|
||||||
|
{
|
||||||
|
if (reference_string.empty())
|
||||||
|
{
|
||||||
|
return reference_tokens.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||||
|
{
|
||||||
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// the whole string is validated, whether or not it matches
|
||||||
|
const std::size_t size = reference_string.size();
|
||||||
|
for (std::size_t pos = reference_string.find_first_of('~');
|
||||||
|
pos != string_t::npos;
|
||||||
|
pos = reference_string.find_first_of('~', pos + 1))
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1')))
|
||||||
|
{
|
||||||
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare token by token, unescaping the string on the fly
|
||||||
|
std::size_t pos = 0; // the slash in front of the next token
|
||||||
|
for (const auto& reference_token : reference_tokens)
|
||||||
|
{
|
||||||
|
if (pos >= size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t i = pos + 1;
|
||||||
|
std::size_t j = 0;
|
||||||
|
const std::size_t token_size = reference_token.size();
|
||||||
|
while (i < size && reference_string[i] != '/')
|
||||||
|
{
|
||||||
|
char c = reference_string[i++];
|
||||||
|
if (c == '~')
|
||||||
|
{
|
||||||
|
c = (reference_string[i++] == '0') ? '~' : '/';
|
||||||
|
}
|
||||||
|
if (j == token_size || reference_token[j] != c)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
if (j != token_size)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pos = i;
|
||||||
|
}
|
||||||
|
return pos == size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief object member access with a key that is only copied if it is inserted
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||||
|
{
|
||||||
|
auto& members = *object.m_data.m_value.object;
|
||||||
|
const auto it = members.find(key);
|
||||||
|
if (it != members.end())
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return object.operator[](key);
|
||||||
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
json_pointer top() const
|
json_pointer top() const
|
||||||
{
|
{
|
||||||
@@ -21005,84 +21130,141 @@ class json_pointer
|
|||||||
the result independent of the order in which the flattened object is
|
the result independent of the order in which the flattened object is
|
||||||
iterated, which is unspecified for some object types.
|
iterated, which is unspecified for some object types.
|
||||||
|
|
||||||
The prefixes form a tree and are numbered, so each of them is stored only
|
The prefixes form a tree and are numbered (number 0 is the empty prefix),
|
||||||
once (as a node) rather than as a copy of all of its reference tokens.
|
so each of them is stored only once (as a node) rather than as a copy of
|
||||||
|
all of its reference tokens. The tree does not own the reference tokens:
|
||||||
|
it refers to the tokens of the parsed keys, which must outlive it.
|
||||||
*/
|
*/
|
||||||
struct prefix_tree
|
struct prefix_tree
|
||||||
{
|
{
|
||||||
// children[id] maps a reference token to the number of the prefix
|
// open-addressing hash table from (parent, last reference token) to
|
||||||
// extended by that token; number 0 is the empty prefix
|
// the number of the node; empty slots have token == nullptr
|
||||||
std::vector<std::map<string_t, std::size_t>> children;
|
struct slot
|
||||||
|
{
|
||||||
|
std::size_t hash;
|
||||||
|
std::size_t parent;
|
||||||
|
const string_t* token;
|
||||||
|
std::size_t id;
|
||||||
|
|
||||||
|
slot() noexcept
|
||||||
|
: hash(0), parent(0), token(nullptr), id(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept
|
||||||
|
: hash(h), parent(p), token(t), id(i)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<slot> slots;
|
||||||
|
std::size_t used = 0;
|
||||||
|
|
||||||
|
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||||
|
{
|
||||||
|
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
||||||
|
for (std::size_t i = 0; i < token.size(); ++i)
|
||||||
|
{
|
||||||
|
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
|
||||||
|
}
|
||||||
|
return h ^ (h >> 29u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void grow()
|
||||||
|
{
|
||||||
|
std::vector<slot> old(slots.size() * 2);
|
||||||
|
old.swap(slots);
|
||||||
|
const std::size_t mask = slots.size() - 1;
|
||||||
|
for (const auto& s : old)
|
||||||
|
{
|
||||||
|
if (s.token != nullptr)
|
||||||
|
{
|
||||||
|
std::size_t i = s.hash & mask;
|
||||||
|
while (slots[i].token != nullptr)
|
||||||
|
{
|
||||||
|
i = (i + 1) & mask;
|
||||||
|
}
|
||||||
|
slots[i] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent[id] is the number of the prefix without its last token
|
||||||
|
std::vector<std::size_t> parent;
|
||||||
// is_array[id] is true iff some flattened key has the reference token
|
// is_array[id] is true iff some flattened key has the reference token
|
||||||
// 0 directly below the prefix with number id
|
// 0 directly below the prefix with number id
|
||||||
std::vector<bool> is_array;
|
std::vector<bool> is_array;
|
||||||
|
|
||||||
// start with the empty prefix only
|
// start with the empty prefix only
|
||||||
prefix_tree()
|
prefix_tree()
|
||||||
: children(1)
|
: slots(16)
|
||||||
|
, parent(1, 0)
|
||||||
, is_array(1, false)
|
, is_array(1, false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// return the number of the prefix with number id extended by
|
// return the number of the prefix with number id extended by
|
||||||
// reference_token, adding it if it is new
|
// reference_token, adding it if it is new
|
||||||
std::size_t add_child(std::size_t id, string_t&& reference_token)
|
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||||
{
|
{
|
||||||
|
const std::size_t hash = hash_of(id, reference_token);
|
||||||
|
const std::size_t mask = slots.size() - 1;
|
||||||
|
std::size_t i = hash & mask;
|
||||||
|
while (slots[i].token != nullptr)
|
||||||
|
{
|
||||||
|
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
||||||
|
{
|
||||||
|
return slots[i].id;
|
||||||
|
}
|
||||||
|
i = (i + 1) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new prefix
|
||||||
|
const std::size_t child = parent.size();
|
||||||
|
slots[i] = slot(hash, id, &reference_token, child);
|
||||||
if (reference_token == "0")
|
if (reference_token == "0")
|
||||||
{
|
{
|
||||||
is_array[id] = true;
|
is_array[id] = true;
|
||||||
}
|
}
|
||||||
|
parent.push_back(id);
|
||||||
|
is_array.push_back(false);
|
||||||
|
|
||||||
// read the number before the emplace_back below, which may
|
// keep the load factor below 1/2
|
||||||
// reallocate children and invalidate the iterator
|
if (++used * 2 > slots.size())
|
||||||
const std::size_t next = children.size();
|
|
||||||
const auto inserted = children[id].emplace(std::move(reference_token), next);
|
|
||||||
const std::size_t child = inserted.first->second;
|
|
||||||
if (inserted.second)
|
|
||||||
{
|
{
|
||||||
children.emplace_back();
|
grow();
|
||||||
is_array.push_back(false);
|
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the number of the prefix with number id extended by
|
|
||||||
// reference_token, which must have been added before
|
|
||||||
std::size_t find_child(std::size_t id, const string_t& reference_token) const
|
|
||||||
{
|
|
||||||
const auto it = children[id].find(reference_token);
|
|
||||||
JSON_ASSERT(it != children[id].end());
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief create and return a reference to the pointed to value
|
@brief create and return a reference to the pointed to value
|
||||||
|
|
||||||
Complexity: Linear in the number of reference tokens (times the logarithm
|
@param[in] j the value to create the pointed to value in
|
||||||
of the number of siblings for the prefix lookup).
|
@param[in] tokens the reference tokens of the pointer
|
||||||
|
@param[in] prefixes prefixes[i] is the number in @a tree of the prefix
|
||||||
|
consisting of the first i reference tokens
|
||||||
|
|
||||||
|
Complexity: Linear in the number of reference tokens.
|
||||||
|
|
||||||
@throw parse_error.106 if an array index begins with '0'
|
@throw parse_error.106 if an array index begins with '0'
|
||||||
@throw parse_error.109 if array index is not a number
|
@throw parse_error.109 if array index is not a number
|
||||||
@throw type_error.313 if value cannot be unflattened
|
@throw type_error.313 if value cannot be unflattened
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
|
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens,
|
||||||
|
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
|
||||||
{
|
{
|
||||||
auto* result = &j;
|
auto* result = &j;
|
||||||
|
|
||||||
// the number of the prefix consumed so far; used to look up whether
|
|
||||||
// the value to be created below is an array or an object
|
|
||||||
std::size_t id = 0;
|
|
||||||
|
|
||||||
// in case no reference tokens exist, return a reference to the JSON value
|
// in case no reference tokens exist, return a reference to the JSON value
|
||||||
// j which will be overwritten by a primitive value
|
// j which will be overwritten by a primitive value
|
||||||
for (const auto& reference_token : reference_tokens)
|
for (std::size_t i = 0; i < tokens.size(); ++i)
|
||||||
{
|
{
|
||||||
|
const auto& reference_token = tokens[i];
|
||||||
switch (result->type())
|
switch (result->type())
|
||||||
{
|
{
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
{
|
{
|
||||||
if (tree.is_array[id])
|
if (tree.is_array[prefixes[i]])
|
||||||
{
|
{
|
||||||
// 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
|
||||||
@@ -21099,7 +21281,7 @@ class json_pointer
|
|||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
// create an entry in the object
|
// create an entry in the object
|
||||||
result = &result->operator[](reference_token);
|
result = &object_member(*result, reference_token);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21126,8 +21308,6 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
|
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
|
||||||
}
|
}
|
||||||
|
|
||||||
id = tree.find_child(id, reference_token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return *result;
|
return *result;
|
||||||
@@ -21179,7 +21359,7 @@ class json_pointer
|
|||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
// use unchecked object access
|
// use unchecked object access
|
||||||
ptr = &ptr->operator[](reference_token);
|
ptr = &object_member(*ptr, reference_token);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21229,8 +21409,13 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
// note: at performs range check
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
ptr = &ptr->at(reference_token);
|
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||||
|
{
|
||||||
|
// at() throws out_of_range.403 with the usual message
|
||||||
|
ptr->at(reference_token);
|
||||||
|
}
|
||||||
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21251,7 +21436,7 @@ class json_pointer
|
|||||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
||||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||||
}
|
}
|
||||||
ptr = &ptr->operator[](idx);
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21299,7 +21484,9 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
// use unchecked object access; the const operator[]
|
// use unchecked object access; the const operator[]
|
||||||
// asserts that the key exists
|
// asserts that the key exists
|
||||||
ptr = &ptr->operator[](reference_token);
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
|
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
|
||||||
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21313,7 +21500,9 @@ class json_pointer
|
|||||||
|
|
||||||
// use unchecked array access; the const operator[]
|
// use unchecked array access; the const operator[]
|
||||||
// asserts that the index exists
|
// asserts that the index exists
|
||||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||||
|
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
|
||||||
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21354,12 +21543,12 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
const auto it = ptr->find(reference_token);
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
ptr = &*it;
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21396,7 +21585,7 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
ptr = &ptr->operator[](idx);
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21429,13 +21618,14 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
if (!ptr->contains(reference_token))
|
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||||
|
if (it == ptr->m_data.m_value.object->end())
|
||||||
{
|
{
|
||||||
// we did not find the key in the object
|
// we did not find the key in the object
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = &ptr->operator[](reference_token);
|
ptr = &it->second;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21457,13 +21647,13 @@ class json_pointer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->m_data.m_value.array->size())
|
||||||
{
|
{
|
||||||
// index out of range
|
// index out of range
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = &ptr->operator[](idx);
|
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21513,6 +21703,15 @@ class json_pointer
|
|||||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// every slash starts a reference token
|
||||||
|
const std::size_t size = reference_string.size();
|
||||||
|
std::size_t token_count = 0;
|
||||||
|
for (std::size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
token_count += (reference_string[i] == '/') ? 1 : 0;
|
||||||
|
}
|
||||||
|
result.reserve(token_count);
|
||||||
|
|
||||||
// extract the reference tokens:
|
// extract the reference tokens:
|
||||||
// - slash: position of the last read slash (or end of string)
|
// - slash: position of the last read slash (or end of string)
|
||||||
// - start: position after the previous slash
|
// - start: position after the previous slash
|
||||||
@@ -21534,31 +21733,52 @@ class json_pointer
|
|||||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||||
auto reference_token = string_t(reference_string.data() + start, count);
|
auto reference_token = string_t(reference_string.data() + start, count);
|
||||||
|
|
||||||
// check reference tokens are properly escaped
|
// check reference tokens are properly escaped; a token without
|
||||||
for (std::size_t pos = reference_token.find_first_of('~');
|
// a '~' (the common case) needs neither check nor unescaping
|
||||||
pos != string_t::npos;
|
std::size_t pos = reference_token.find_first_of('~');
|
||||||
pos = reference_token.find_first_of('~', pos + 1))
|
if (pos != string_t::npos)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(reference_token[pos] == '~');
|
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||||
|
|
||||||
// ~ must be followed by 0 or 1
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
|
||||||
(reference_token[pos + 1] != '0' &&
|
|
||||||
reference_token[pos + 1] != '1')))
|
|
||||||
{
|
{
|
||||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
JSON_ASSERT(reference_token[pos] == '~');
|
||||||
|
|
||||||
|
// ~ must be followed by 0 or 1
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
||||||
|
(reference_token[pos + 1] != '0' &&
|
||||||
|
reference_token[pos + 1] != '1')))
|
||||||
|
{
|
||||||
|
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detail::unescape(reference_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally, store the reference token
|
// finally, store the reference token
|
||||||
detail::unescape(reference_token);
|
result.push_back(std::move(reference_token));
|
||||||
result.push_back(reference_token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise
|
||||||
|
// distinct, so objects that can append a key known to be absent
|
||||||
|
// (ordered_map) skip the lookup, which is linear and would make flatten
|
||||||
|
// quadratic; all other object types use operator[].
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, int /*unused*/)
|
||||||
|
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
|
||||||
|
{
|
||||||
|
obj.emplace_new_key(key, std::forward<ValueType>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType, typename ValueType>
|
||||||
|
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, ...)
|
||||||
|
{
|
||||||
|
obj[key] = std::forward<ValueType>(v);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] reference_string the reference string to the current value
|
@param[in] reference_string the reference string to the current value
|
||||||
@param[in] value the value to consider
|
@param[in] value the value to consider
|
||||||
@@ -21603,7 +21823,8 @@ class json_pointer
|
|||||||
|
|
||||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||||
// are added to the result right away; other containers get a frame
|
// are added to the result right away; other containers get a frame
|
||||||
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
auto& object = *result.m_data.m_value.object;
|
||||||
|
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
|
||||||
{
|
{
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
@@ -21612,7 +21833,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.array->empty())
|
if (v.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
// flatten empty array as null
|
// flatten empty array as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -21626,7 +21847,7 @@ class json_pointer
|
|||||||
if (v.m_data.m_value.object->empty())
|
if (v.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
// flatten empty object as null
|
// flatten empty object as null
|
||||||
result[path] = nullptr;
|
add_flattened(object, path, nullptr, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -21646,7 +21867,7 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// add a primitive value with its reference string
|
// add a primitive value with its reference string
|
||||||
result[path] = v;
|
add_flattened(object, path, v, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21692,6 +21913,10 @@ class json_pointer
|
|||||||
enter(it->second);
|
enter(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the entries were added without going through basic_json, so the
|
||||||
|
// parent pointers (JSON_DIAGNOSTICS) have to be set now
|
||||||
|
result.set_parents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -21715,21 +21940,53 @@ class json_pointer
|
|||||||
|
|
||||||
BasicJsonType result;
|
BasicJsonType result;
|
||||||
|
|
||||||
|
// parse every key once; the reference tokens are kept for the second
|
||||||
|
// pass (the vectors are never resized, so the tree may refer to them)
|
||||||
|
std::vector<std::vector<string_t>> keys;
|
||||||
|
keys.reserve(value.m_data.m_value.object->size());
|
||||||
|
for (const auto& element : *value.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
json_pointer ptr(element.first);
|
||||||
|
keys.push_back(std::move(ptr.reference_tokens));
|
||||||
|
}
|
||||||
|
|
||||||
// collect the pointer prefixes that have a reference token 0 among
|
// collect the pointer prefixes that have a reference token 0 among
|
||||||
// their children; the values below them are arrays, all others are
|
// their children; the values below them are arrays, all others are
|
||||||
// objects (see prefix_tree)
|
// objects (see prefix_tree)
|
||||||
prefix_tree tree;
|
prefix_tree tree;
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
// the number of the prefix with i + 1 tokens of the previous key; keys
|
||||||
|
// are often sorted, so most of the prefix is the same as for the
|
||||||
|
// previous key and need not be looked up again
|
||||||
|
std::vector<std::size_t> path;
|
||||||
|
const std::vector<string_t>* previous = nullptr;
|
||||||
|
// the number of the complete prefix (i.e., the key) of each key
|
||||||
|
std::vector<std::size_t> leaves;
|
||||||
|
leaves.reserve(keys.size());
|
||||||
|
for (const auto& tokens : keys)
|
||||||
{
|
{
|
||||||
json_pointer ptr(element.first);
|
std::size_t common = 0;
|
||||||
std::size_t id = 0;
|
if (previous != nullptr)
|
||||||
for (auto& reference_token : ptr.reference_tokens)
|
|
||||||
{
|
{
|
||||||
id = tree.add_child(id, std::move(reference_token));
|
const std::size_t limit = (std::min)(tokens.size(), previous->size());
|
||||||
|
while (common < limit && tokens[common] == (*previous)[common])
|
||||||
|
{
|
||||||
|
++common;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
path.resize(common);
|
||||||
|
std::size_t id = common == 0 ? 0 : path[common - 1];
|
||||||
|
for (std::size_t i = common; i < tokens.size(); ++i)
|
||||||
|
{
|
||||||
|
id = tree.add_child(id, tokens[i]);
|
||||||
|
path.push_back(id);
|
||||||
|
}
|
||||||
|
leaves.push_back(id);
|
||||||
|
previous = &tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate the JSON object values
|
// iterate the JSON object values
|
||||||
|
std::vector<std::size_t> prefixes;
|
||||||
|
std::size_t key_index = 0;
|
||||||
for (const auto& element : *value.m_data.m_value.object)
|
for (const auto& element : *value.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
||||||
@@ -21737,11 +21994,21 @@ class json_pointer
|
|||||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
|
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& tokens = keys[key_index];
|
||||||
|
// number the prefixes of this key from its last one to the first
|
||||||
|
prefixes.resize(tokens.size());
|
||||||
|
for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i)
|
||||||
|
{
|
||||||
|
id = tree.parent[id];
|
||||||
|
prefixes[i - 1] = id;
|
||||||
|
}
|
||||||
|
++key_index;
|
||||||
|
|
||||||
// Assign the value to the reference pointed to by JSON pointer. Note
|
// Assign the value to the reference pointed to by JSON pointer. Note
|
||||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||||
// function get_and_create returns a reference to the result itself.
|
// function get_and_create returns a reference to the result itself.
|
||||||
// An assignment will then create a primitive value.
|
// An assignment will then create a primitive value.
|
||||||
json_pointer(element.first).get_and_create(result, tree) = element.second;
|
get_and_create(result, tokens, prefixes, tree) = element.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -21780,7 +22047,7 @@ class json_pointer
|
|||||||
= delete;
|
= delete;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
return *this == json_pointer(rhs);
|
return equals_string(rhs);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -21864,7 +22131,7 @@ inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|||||||
= delete;
|
= delete;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
return lhs.equals_string(rhs);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -21877,7 +22144,7 @@ inline bool operator==(const StringType& lhs,
|
|||||||
= delete;
|
= delete;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
return rhs.equals_string(lhs);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -29158,6 +29425,17 @@ public:
|
|||||||
return {std::prev(this->end()), true};
|
return {std::prev(this->end()), true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief add an element at the end without checking whether the key exists
|
||||||
|
/// @note The caller must guarantee that @a key is not yet contained, otherwise
|
||||||
|
/// the container holds duplicate keys. This avoids the linear search of
|
||||||
|
/// emplace() and operator[] when many keys known to be new are added.
|
||||||
|
template<class V, detail::enable_if_t<
|
||||||
|
detail::is_constructible<T, V>::value, int> = 0>
|
||||||
|
void emplace_new_key(const key_type& key, V && t)
|
||||||
|
{
|
||||||
|
append(key, std::forward<V>(t));
|
||||||
|
}
|
||||||
|
|
||||||
T& operator[](const key_type& key)
|
T& operator[](const key_type& key)
|
||||||
{
|
{
|
||||||
return emplace(key, T{}).first->second;
|
return emplace(key, T{}).first->second;
|
||||||
@@ -35910,24 +36188,18 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// wrapper for "add" operation; add value at ptr
|
// wrapper for "add" operation; add value at ptr
|
||||||
const auto operation_add = [&result](json_pointer & ptr, const basic_json & val)
|
const auto operation_add = [&result](json_pointer & ptr, basic_json val)
|
||||||
{
|
{
|
||||||
// adding to the root of the target document means replacing it
|
// adding to the root of the target document means replacing it
|
||||||
if (ptr.empty())
|
if (ptr.empty())
|
||||||
{
|
{
|
||||||
result = val;
|
result = std::move(val);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the top element of the pointer exists
|
// get reference to the parent of the JSON pointer ptr; a missing
|
||||||
json_pointer const top_pointer = ptr.top();
|
// parent (or one of its ancestors) is reported by at()
|
||||||
if (top_pointer != ptr)
|
auto last_path = std::move(ptr.reference_tokens.back());
|
||||||
{
|
|
||||||
result.at(top_pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get reference to the parent of the JSON pointer ptr
|
|
||||||
const auto last_path = ptr.back();
|
|
||||||
ptr.pop_back();
|
ptr.pop_back();
|
||||||
// parent must exist when performing patch add per RFC6902 specs
|
// parent must exist when performing patch add per RFC6902 specs
|
||||||
basic_json& parent = result.at(ptr);
|
basic_json& parent = result.at(ptr);
|
||||||
@@ -35938,7 +36210,7 @@ public:
|
|||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
// use operator[] to add value
|
// use operator[] to add value
|
||||||
parent[last_path] = val;
|
parent[std::move(last_path)] = std::move(val);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35947,7 +36219,7 @@ public:
|
|||||||
if (last_path == "-")
|
if (last_path == "-")
|
||||||
{
|
{
|
||||||
// special case: append to back
|
// special case: append to back
|
||||||
parent.push_back(val);
|
parent.push_back(std::move(val));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -35959,7 +36231,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default case: insert add offset
|
// default case: insert add offset
|
||||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
|
parent.insert(parent.begin() + static_cast<difference_type>(idx), std::move(val));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -35982,7 +36254,8 @@ public:
|
|||||||
const auto operation_remove = [this, & result](json_pointer & ptr)
|
const auto operation_remove = [this, & result](json_pointer & ptr)
|
||||||
{
|
{
|
||||||
// get reference to the parent of the JSON pointer ptr
|
// get reference to the parent of the JSON pointer ptr
|
||||||
const auto last_path = ptr.back();
|
static_cast<void>(ptr.back()); // throws out_of_range.405 if ptr is the root
|
||||||
|
const auto last_path = std::move(ptr.reference_tokens.back());
|
||||||
ptr.pop_back();
|
ptr.pop_back();
|
||||||
basic_json& parent = result.at(ptr);
|
basic_json& parent = result.at(ptr);
|
||||||
|
|
||||||
@@ -36082,8 +36355,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// collect mandatory members
|
// collect mandatory members
|
||||||
const auto op = get_value("op", "op", true).template get<string_t>();
|
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
|
||||||
const auto path = get_value(op, "path", true).template get<string_t>();
|
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
||||||
json_pointer ptr(path);
|
json_pointer ptr(path);
|
||||||
|
|
||||||
switch (get_op(op))
|
switch (get_op(op))
|
||||||
@@ -36109,7 +36382,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::move:
|
case patch_operations::move:
|
||||||
{
|
{
|
||||||
const auto from_path = get_value("move", "from", true).template get<string_t>();
|
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)))
|
||||||
@@ -36118,29 +36391,28 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the "from" location must exist - use at()
|
// the "from" location must exist - use at()
|
||||||
basic_json const v = result.at(from_ptr);
|
// a root "from" is rejected by operation_remove below, which must leave result intact
|
||||||
|
basic_json v = from_ptr.empty() ? basic_json(result) : std::move(result.at(from_ptr));
|
||||||
|
|
||||||
// The move operation is functionally identical to a
|
// The move operation is functionally identical to a
|
||||||
// "remove" operation on the "from" location, followed
|
// "remove" operation on the "from" location, followed
|
||||||
// immediately by an "add" operation at the target
|
// immediately by an "add" operation at the target
|
||||||
// location with the value that was just removed.
|
// location with the value that was just removed.
|
||||||
operation_remove(from_ptr);
|
operation_remove(from_ptr);
|
||||||
operation_add(ptr, v);
|
operation_add(ptr, std::move(v));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case patch_operations::copy:
|
case patch_operations::copy:
|
||||||
{
|
{
|
||||||
const auto from_path = get_value("copy", "from", true).template get<string_t>();
|
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 "from" location must exist - use at()
|
|
||||||
basic_json const v = result.at(from_ptr);
|
|
||||||
|
|
||||||
// The copy is functionally identical to an "add"
|
// The copy is functionally identical to an "add"
|
||||||
// operation at the target location using the value
|
// operation at the target location using the value
|
||||||
// specified in the "from" member.
|
// specified in the "from" member; the "from" location
|
||||||
operation_add(ptr, v);
|
// must exist - use at()
|
||||||
|
operation_add(ptr, result.at(from_ptr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <numeric>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,14 @@ using nlohmann::json;
|
|||||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
TEST_CASE("JSON pointers")
|
TEST_CASE("JSON pointers")
|
||||||
{
|
{
|
||||||
@@ -1071,3 +1077,242 @@ TEST_CASE("flatten of structured values")
|
|||||||
R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})");
|
R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("flatten of ordered_json")
|
||||||
|
{
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
|
||||||
|
SECTION("keeps the document order of the leaves")
|
||||||
|
{
|
||||||
|
const ordered_json j = ordered_json::parse(R"({
|
||||||
|
"z": 1, "a": {"y": [true, null, {}], "b": "x", "": []},
|
||||||
|
"m~n": 2.5, "o/p": {"k": {}}, "0": [[1], [2, 3]]
|
||||||
|
})");
|
||||||
|
|
||||||
|
const ordered_json flat = j.flatten();
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
for (const auto& el : flat.items())
|
||||||
|
{
|
||||||
|
keys.push_back(el.key());
|
||||||
|
}
|
||||||
|
const std::vector<std::string> expected =
|
||||||
|
{
|
||||||
|
"/z", "/a/y/0", "/a/y/1", "/a/y/2", "/a/b", "/a/", "/m~0n", "/o~1p/k", "/0/0/0", "/0/1/0", "/0/1/1"
|
||||||
|
};
|
||||||
|
CHECK(keys == expected);
|
||||||
|
CHECK(flat.size() == expected.size());
|
||||||
|
CHECK(flat["/m~0n"] == 2.5);
|
||||||
|
CHECK(flat["/a/y/2"].is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("round-trips and agrees with json")
|
||||||
|
{
|
||||||
|
ordered_json j;
|
||||||
|
for (int i = 0; i < 200; ++i)
|
||||||
|
{
|
||||||
|
j["k" + std::to_string(i)] = {{"v", i}, {"l", {i, i + 1, "s"}}};
|
||||||
|
}
|
||||||
|
const ordered_json flat = j.flatten();
|
||||||
|
CHECK(flat.size() == 800);
|
||||||
|
CHECK(flat.unflatten() == j);
|
||||||
|
|
||||||
|
// same entries as for a sorted object
|
||||||
|
const nlohmann::json plain = nlohmann::json::parse(j.dump());
|
||||||
|
CHECK(nlohmann::json::parse(flat.dump()) == plain.flatten());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("primitive and empty root")
|
||||||
|
{
|
||||||
|
CHECK(ordered_json(1).flatten() == ordered_json({{"", 1}}));
|
||||||
|
CHECK(ordered_json::array().flatten() == ordered_json({{"", nullptr}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// reference implementation of the array index rules (RFC 6901, Sect. 4) based on strtoull
|
||||||
|
enum class index_class
|
||||||
|
{
|
||||||
|
ok,
|
||||||
|
leading_zero,
|
||||||
|
not_a_number,
|
||||||
|
unresolved,
|
||||||
|
exceeds_size_type
|
||||||
|
};
|
||||||
|
|
||||||
|
index_class classify_index(const std::string& s, std::size_t& idx)
|
||||||
|
{
|
||||||
|
if (s.size() > 1 && s[0] == '0')
|
||||||
|
{
|
||||||
|
return index_class::leading_zero;
|
||||||
|
}
|
||||||
|
if (!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9'))
|
||||||
|
{
|
||||||
|
return index_class::not_a_number;
|
||||||
|
}
|
||||||
|
char* end = nullptr;
|
||||||
|
errno = 0;
|
||||||
|
const unsigned long long res = std::strtoull(s.c_str(), &end, 10); // NOLINT(runtime/int)
|
||||||
|
if (end == s.c_str() || errno == ERANGE || static_cast<std::size_t>(end - s.c_str()) != s.size())
|
||||||
|
{
|
||||||
|
return index_class::unresolved;
|
||||||
|
}
|
||||||
|
if (res >= static_cast<unsigned long long>((std::numeric_limits<std::size_t>::max)())) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return index_class::exceeds_size_type;
|
||||||
|
}
|
||||||
|
idx = static_cast<std::size_t>(res);
|
||||||
|
return index_class::ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the observable behavior of json_pointer::at() and contains() for an array index token
|
||||||
|
void check_index_token(const std::string& token)
|
||||||
|
{
|
||||||
|
const json array = {10, 20, 30};
|
||||||
|
const json::json_pointer ptr("/" + token);
|
||||||
|
|
||||||
|
std::size_t idx = 0;
|
||||||
|
const auto cls = classify_index(token, idx);
|
||||||
|
const bool exists = cls == index_class::ok && idx < array.size();
|
||||||
|
|
||||||
|
CAPTURE(token)
|
||||||
|
CHECK(array.contains(ptr) == (exists && token != "-"));
|
||||||
|
|
||||||
|
if (token == "-")
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cls)
|
||||||
|
{
|
||||||
|
case index_class::leading_zero:
|
||||||
|
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
|
||||||
|
break;
|
||||||
|
case index_class::not_a_number:
|
||||||
|
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
|
||||||
|
break;
|
||||||
|
case index_class::unresolved:
|
||||||
|
case index_class::exceeds_size_type:
|
||||||
|
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||||
|
break;
|
||||||
|
case index_class::ok:
|
||||||
|
default:
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
CHECK(array.at(ptr) == array[idx]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("JSON pointer array index tokens match strtoull semantics")
|
||||||
|
{
|
||||||
|
SECTION("boundary values")
|
||||||
|
{
|
||||||
|
const std::string max_digits = std::to_string((std::numeric_limits<std::size_t>::max)());
|
||||||
|
const std::vector<std::string> tokens =
|
||||||
|
{
|
||||||
|
"", "-", "0", "1", "2", "3", "00", "01", "007", "+1", "-1", " 1", "1 ", "1a", "a1", "0x1", "1e1", "1.0",
|
||||||
|
"999999999", "1000000000", "99999999999999999", "999999999999999999", "1000000000000000000",
|
||||||
|
"9999999999999999999", "18446744073709551614", max_digits, "18446744073709551616",
|
||||||
|
"99999999999999999999999", "000000000000000000000"
|
||||||
|
};
|
||||||
|
for (const auto& token : tokens)
|
||||||
|
{
|
||||||
|
check_index_token(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("random tokens")
|
||||||
|
{
|
||||||
|
std::mt19937 gen(42);
|
||||||
|
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
|
||||||
|
for (int i = 0; i < 5000; ++i)
|
||||||
|
{
|
||||||
|
std::string token(static_cast<std::size_t>(gen() % 23), '0');
|
||||||
|
for (auto& c : token)
|
||||||
|
{
|
||||||
|
c = alphabet[gen() % alphabet.size()];
|
||||||
|
}
|
||||||
|
check_index_token(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON pointer operations on pointers with escaped tokens")
|
||||||
|
{
|
||||||
|
const std::vector<std::string> strings =
|
||||||
|
{
|
||||||
|
"", "/", "//", "/a", "/a/b", "/a~1b", "/~0", "/~01", "/~10", "/a~0~1/b/", "/0/1/2", "/ ", "/a/", "/~1/~0"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& str : strings)
|
||||||
|
{
|
||||||
|
CAPTURE(str)
|
||||||
|
const json::json_pointer ptr(str);
|
||||||
|
CHECK(ptr.to_string() == str);
|
||||||
|
CHECK(ptr / "x" / "y" / std::size_t{3} == json::json_pointer(str + "/x/y/3"));
|
||||||
|
CHECK(ptr.parent_pointer() == (str.empty() ? ptr : json::json_pointer(str.substr(0, str.rfind('/')))));
|
||||||
|
|
||||||
|
// comparison with a string, in place, and with every other pointer
|
||||||
|
#if !defined(JSON_TEST_DEPRECATED_FUNCTIONS_DELETED)
|
||||||
|
for (const auto& other : strings)
|
||||||
|
{
|
||||||
|
CAPTURE(other)
|
||||||
|
CHECK((ptr == other) == (str == other));
|
||||||
|
CHECK((other == ptr) == (str == other));
|
||||||
|
CHECK((ptr != other) == (str != other));
|
||||||
|
}
|
||||||
|
CHECK_THROWS_AS(ptr == "x", json::parse_error&);
|
||||||
|
CHECK_THROWS_AS(ptr == "/~", json::parse_error&);
|
||||||
|
CHECK_THROWS_AS(ptr == "/b/~2", json::parse_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON pointer lookups")
|
||||||
|
{
|
||||||
|
json j = {{"a", {{"b", {1, 2, 3}}}}, {"", 7}, {"~/", {{"x", nullptr}}}};
|
||||||
|
const json& cj = j;
|
||||||
|
|
||||||
|
CHECK(cj[json::json_pointer("/a/b/2")] == 3);
|
||||||
|
CHECK(cj.at(json::json_pointer("/a/b/1")) == 2);
|
||||||
|
CHECK(cj.at(json::json_pointer("/")) == 7);
|
||||||
|
CHECK(cj.contains(json::json_pointer("/~0~1/x")));
|
||||||
|
CHECK(!cj.contains(json::json_pointer("/a/c")));
|
||||||
|
CHECK(cj.value(json::json_pointer("/a/b/0"), 5) == 1);
|
||||||
|
CHECK(cj.value(json::json_pointer("/a/b/3"), 5) == 5);
|
||||||
|
CHECK(cj.value(json::json_pointer("/a/zz"), 5) == 5);
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/c")), json::out_of_range&);
|
||||||
|
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/b/3")), json::out_of_range&);
|
||||||
|
|
||||||
|
// non-const access reuses existing entries and creates missing ones
|
||||||
|
j[json::json_pointer("/a/b/1")] = 20;
|
||||||
|
j[json::json_pointer("/n/m")] = 1;
|
||||||
|
CHECK(j["a"]["b"] == json({1, 20, 3}));
|
||||||
|
CHECK(j["n"]["m"] == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON patch with moves and copies")
|
||||||
|
{
|
||||||
|
json doc = {{"a", {{"b", {1, 2, 3}}}}, {"c", {{"d", "x"}}}};
|
||||||
|
|
||||||
|
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b","path":"/c/e"}])")) ==
|
||||||
|
json::parse(R"({"a":{},"c":{"d":"x","e":[1,2,3]}})"));
|
||||||
|
CHECK(doc.patch(json::parse(R"([{"op":"copy","from":"/a/b","path":"/c/e"}])")) ==
|
||||||
|
json::parse(R"({"a":{"b":[1,2,3]},"c":{"d":"x","e":[1,2,3]}})"));
|
||||||
|
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b/0","path":"/a/b/2"}])")) ==
|
||||||
|
json::parse(R"({"a":{"b":[2,3,1]},"c":{"d":"x"}})"));
|
||||||
|
|
||||||
|
// a failing move leaves a copy-patched document untouched
|
||||||
|
CHECK_THROWS_AS(doc.patch(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
|
||||||
|
json inplace = doc;
|
||||||
|
CHECK_THROWS_AS(inplace.patch_inplace(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
|
||||||
|
CHECK(inplace == doc);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user