mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 05:58:12 +00:00
Merge branch 'develop' into claude/todo-191-plan-508110
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
495 files changed
+623375
-6453
No files matched your search
@@ -11,19 +11,19 @@
|
||||
#include <algorithm> // all_of
|
||||
#include <cctype> // isdigit
|
||||
#include <cerrno> // errno, ERANGE
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdlib> // strtoull
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
#include <limits> // max
|
||||
#include <map> // map
|
||||
#include <numeric> // accumulate
|
||||
#include <string> // string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_escape.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
@@ -31,6 +31,11 @@
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct json_pointer_access;
|
||||
} // namespace detail
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||
template<typename RefStringType>
|
||||
@@ -43,6 +48,8 @@ class json_pointer
|
||||
template<typename>
|
||||
friend class json_pointer;
|
||||
|
||||
friend struct detail::json_pointer_access;
|
||||
|
||||
template<typename T>
|
||||
struct string_t_helper
|
||||
{
|
||||
@@ -70,12 +77,21 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||
string_t to_string() const
|
||||
{
|
||||
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
||||
string_t{},
|
||||
[](const string_t& a, const string_t& b)
|
||||
// one buffer: every token adds its escaped form (at least its length) and a slash
|
||||
std::size_t length = 0;
|
||||
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
|
||||
@@ -130,21 +146,29 @@ class json_pointer
|
||||
friend json_pointer operator/(const json_pointer& lhs,
|
||||
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
|
||||
/// @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)
|
||||
{
|
||||
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
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
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
|
||||
@@ -156,8 +180,8 @@ class json_pointer
|
||||
return *this;
|
||||
}
|
||||
|
||||
json_pointer res = *this;
|
||||
res.pop_back();
|
||||
json_pointer res;
|
||||
res.reference_tokens = std::vector<string_t>(reference_tokens.begin(), reference_tokens.end() - 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -167,7 +191,7 @@ class json_pointer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(empty()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
||||
throw_no_parent();
|
||||
}
|
||||
|
||||
reference_tokens.erase(reference_tokens.begin());
|
||||
@@ -179,7 +203,7 @@ class json_pointer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(empty()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
||||
throw_no_parent();
|
||||
}
|
||||
|
||||
return reference_tokens.front();
|
||||
@@ -205,7 +229,7 @@ class json_pointer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(empty()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
||||
throw_no_parent();
|
||||
}
|
||||
|
||||
reference_tokens.pop_back();
|
||||
@@ -217,7 +241,7 @@ class json_pointer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(empty()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
||||
throw_no_parent();
|
||||
}
|
||||
|
||||
return reference_tokens.back();
|
||||
@@ -245,6 +269,21 @@ class json_pointer
|
||||
}
|
||||
|
||||
private:
|
||||
/// @throw out_of_range.405 always
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_no_parent()
|
||||
{
|
||||
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
|
||||
template<typename BasicJsonContext>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE 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>(context);
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief result of @ref parse_array_index
|
||||
|
||||
@@ -256,7 +295,7 @@ class json_pointer
|
||||
{
|
||||
ok, ///< @a s is a valid, representable array index
|
||||
leading_zero, ///< @a s begins with '0' but has more than one character
|
||||
not_a_number, ///< @a s does not begin with a digit
|
||||
not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit
|
||||
unresolved, ///< @a s could not be converted to an integer
|
||||
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
||||
};
|
||||
@@ -283,12 +322,36 @@ class json_pointer
|
||||
return array_index_status::leading_zero;
|
||||
}
|
||||
|
||||
// error condition (cf. RFC 6901, Sect. 4)
|
||||
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
|
||||
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
|
||||
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
||||
// token are left to the conversion below and are reported as unresolved
|
||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
|
||||
{
|
||||
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();
|
||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||
errno = 0; // strtoull doesn't reset errno
|
||||
@@ -330,13 +393,13 @@ class json_pointer
|
||||
// the branches differ in their messages, not after JSON_THROW's expansion
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case array_index_status::leading_zero:
|
||||
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
||||
case array_index_status::not_a_number:
|
||||
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
||||
case array_index_status::unresolved:
|
||||
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
||||
throw_unresolved(s, nullptr);
|
||||
case array_index_status::exceeds_size_type:
|
||||
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr));
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr));
|
||||
case array_index_status::ok:
|
||||
default:
|
||||
break;
|
||||
@@ -345,12 +408,89 @@ class json_pointer
|
||||
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_pointer top() const
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(empty()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
||||
throw_no_parent();
|
||||
}
|
||||
|
||||
json_pointer result = *this;
|
||||
@@ -368,84 +508,141 @@ class json_pointer
|
||||
the result independent of the order in which the flattened object is
|
||||
iterated, which is unspecified for some object types.
|
||||
|
||||
The prefixes form a tree and are numbered, so each of them is stored only
|
||||
once (as a node) rather than as a copy of all of its reference tokens.
|
||||
The prefixes form a tree and are numbered (number 0 is the empty prefix),
|
||||
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
|
||||
{
|
||||
// children[id] maps a reference token to the number of the prefix
|
||||
// extended by that token; number 0 is the empty prefix
|
||||
std::vector<std::map<string_t, std::size_t>> children;
|
||||
// open-addressing hash table from (parent, last reference token) to
|
||||
// the number of the node; empty slots have token == nullptr
|
||||
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> table;
|
||||
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 & (std::numeric_limits<std::size_t>::max)());
|
||||
for (std::size_t i = 0; i < token.size(); ++i)
|
||||
{
|
||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL & (std::numeric_limits<std::size_t>::max)());
|
||||
}
|
||||
return h ^ (h >> 29u);
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.token != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (table[i].token != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
table[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
|
||||
// 0 directly below the prefix with number id
|
||||
std::vector<bool> is_array;
|
||||
|
||||
// start with the empty prefix only
|
||||
prefix_tree()
|
||||
: children(1)
|
||||
: table(16)
|
||||
, parent(1, 0)
|
||||
, is_array(1, false)
|
||||
{}
|
||||
|
||||
// return the number of the prefix with number id extended by
|
||||
// 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 = table.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (table[i].token != nullptr)
|
||||
{
|
||||
if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
|
||||
{
|
||||
return table[i].id;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
|
||||
// new prefix
|
||||
const std::size_t child = parent.size();
|
||||
table[i] = slot(hash, id, &reference_token, child);
|
||||
if (reference_token == "0")
|
||||
{
|
||||
is_array[id] = true;
|
||||
}
|
||||
parent.push_back(id);
|
||||
is_array.push_back(false);
|
||||
|
||||
// read the number before the emplace_back below, which may
|
||||
// reallocate children and invalidate the iterator
|
||||
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)
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > table.size())
|
||||
{
|
||||
children.emplace_back();
|
||||
is_array.push_back(false);
|
||||
grow();
|
||||
}
|
||||
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
|
||||
|
||||
Complexity: Linear in the number of reference tokens (times the logarithm
|
||||
of the number of siblings for the prefix lookup).
|
||||
@param[in] j the value to create the pointed to value in
|
||||
@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.109 if array index is not a number
|
||||
@throw type_error.313 if value cannot be unflattened
|
||||
*/
|
||||
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;
|
||||
|
||||
// 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
|
||||
// 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())
|
||||
{
|
||||
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
|
||||
// value is an array
|
||||
@@ -462,7 +659,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// create an entry in the object
|
||||
result = &result->operator[](reference_token);
|
||||
result = &object_member(*result, reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -487,10 +684,8 @@ class json_pointer
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
JSON_THROW(detail::type_error::create(313, "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;
|
||||
@@ -542,7 +737,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// use unchecked object access
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -570,7 +765,7 @@ class json_pointer
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,8 +787,13 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(reference_token);
|
||||
const auto it = ptr->m_data.m_value.object->find(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;
|
||||
}
|
||||
|
||||
@@ -602,7 +802,7 @@ class json_pointer
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
JSON_THROW(detail::out_of_range::create(402, detail::concat(
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
||||
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
@@ -611,10 +811,10 @@ class json_pointer
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(401, 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));
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -627,7 +827,7 @@ class json_pointer
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +862,9 @@ class json_pointer
|
||||
{
|
||||
// use unchecked object access; the const operator[]
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -671,12 +873,14 @@ class json_pointer
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" cannot be used for const access
|
||||
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||
}
|
||||
|
||||
// use unchecked array access; the const operator[]
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -689,7 +893,7 @@ class json_pointer
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,12 +921,12 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
const auto it = ptr->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &*it;
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -744,9 +948,9 @@ class json_pointer
|
||||
// the branches differ in their messages, not after JSON_THROW's expansion
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case array_index_status::leading_zero:
|
||||
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
||||
case array_index_status::not_a_number:
|
||||
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
||||
case array_index_status::unresolved:
|
||||
case array_index_status::exceeds_size_type:
|
||||
return nullptr;
|
||||
@@ -759,7 +963,7 @@ class json_pointer
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -792,13 +996,14 @@ class json_pointer
|
||||
{
|
||||
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
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -820,13 +1025,13 @@ class json_pointer
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= ptr->size())
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// index out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -873,9 +1078,18 @@ class json_pointer
|
||||
// check if a nonempty reference string begins with slash
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(107, 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 += static_cast<std::size_t>(reference_string[i] == '/');
|
||||
}
|
||||
result.reserve(token_count);
|
||||
|
||||
// extract the reference tokens:
|
||||
// - slash: position of the last read slash (or end of string)
|
||||
// - start: position after the previous slash
|
||||
@@ -897,31 +1111,52 @@ class json_pointer
|
||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||
auto reference_token = string_t(reference_string.data() + start, count);
|
||||
|
||||
// check reference tokens are properly escaped
|
||||
for (std::size_t pos = reference_token.find_first_of('~');
|
||||
pos != string_t::npos;
|
||||
pos = reference_token.find_first_of('~', pos + 1))
|
||||
// check reference tokens are properly escaped; a token without
|
||||
// a '~' (the common case) needs neither check nor unescaping
|
||||
std::size_t pos = reference_token.find_first_of('~');
|
||||
if (pos != string_t::npos)
|
||||
{
|
||||
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')))
|
||||
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(108, 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
|
||||
detail::unescape(reference_token);
|
||||
result.push_back(reference_token);
|
||||
result.push_back(std::move(reference_token));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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, detail::priority_tag<1> /*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, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
obj[key] = std::forward<ValueType>(v);
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] reference_string the reference string to the current value
|
||||
@param[in] value the value to consider
|
||||
@@ -966,7 +1201,8 @@ class json_pointer
|
||||
|
||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||
// 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())
|
||||
{
|
||||
@@ -975,7 +1211,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.array->empty())
|
||||
{
|
||||
// flatten empty array as null
|
||||
result[path] = nullptr;
|
||||
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -989,7 +1225,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.object->empty())
|
||||
{
|
||||
// flatten empty object as null
|
||||
result[path] = nullptr;
|
||||
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1009,7 +1245,7 @@ class json_pointer
|
||||
default:
|
||||
{
|
||||
// add a primitive value with its reference string
|
||||
result[path] = v;
|
||||
add_flattened(object, path, v, detail::priority_tag<1> {});
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1055,6 +1291,10 @@ class json_pointer
|
||||
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();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1073,38 +1313,80 @@ class json_pointer
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!value.is_object()))
|
||||
{
|
||||
JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value));
|
||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_not_object, "only objects can be unflattened", &value));
|
||||
}
|
||||
|
||||
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
|
||||
// their children; the values below them are arrays, all others are
|
||||
// objects (see prefix_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 id = 0;
|
||||
for (auto& reference_token : ptr.reference_tokens)
|
||||
std::size_t common = 0;
|
||||
if (previous != nullptr)
|
||||
{
|
||||
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
|
||||
std::vector<std::size_t> prefixes;
|
||||
std::size_t key_index = 0;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
||||
{
|
||||
JSON_THROW(detail::type_error::create(315, "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
|
||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||
// function get_and_create returns a reference to the result itself.
|
||||
// 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;
|
||||
@@ -1143,7 +1425,7 @@ class json_pointer
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return *this == json_pointer(rhs);
|
||||
return equals_string(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1227,7 +1509,7 @@ inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
||||
return lhs.equals_string(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1240,7 +1522,7 @@ inline bool operator==(const StringType& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
||||
return rhs.equals_string(lhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1285,4 +1567,18 @@ inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace detail
|
||||
{
|
||||
/// the reference tokens of a json_pointer, for code that resolves pointers
|
||||
/// without a basic_json value (such as the zero-copy view)
|
||||
struct json_pointer_access
|
||||
{
|
||||
template<typename RefStringType>
|
||||
static const std::vector<typename json_pointer<RefStringType>::string_t>& reference_tokens(const json_pointer<RefStringType>& ptr) noexcept
|
||||
{
|
||||
return ptr.reference_tokens;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
Reference in new issue
Block a user