Merge branch 'develop' into claude/todo-191-plan-508110

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-21 10:51:41 +02:00
commit 9222763ab9
495 files changed
+623375 -6453

No files matched your search

+408 -112
View File
@@ -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