Make the strongly exception-safe pointer operator[] faster than before
The previous commit made get_unchecked strongly exception safe but slower with GCC 16 when it creates values (up to +6 % instructions for appending with "-"). This restructures it so that no benchmark row is slower than before the strong guarantee, with GCC 16, Clang 22 and Apple Clang. - get_unchecked walks existing values in one loop that inserts nothing. The last reference token is resolved in that walk: "-" appends with emplace_back(), an index grows the array with operator[], and a missing key is inserted with emplace_hint() at the position the walk's lower_bound() found, so the key is searched once. - With std::less<string_t> or std::less<> as comparator, the walk checks the key at the lower bound for equality instead of equivalence, which is the same for these comparators and cheaper. - get_unchecked_create (not inlined) handles all other changes. Its guard swaps a null value with a fresh null value instead of copying it and reverts the change in a non-inlined function, so a successful call pays one compare for it. As every null value converted afterwards is a fresh one, it is converted in place instead of assigning a new array or object. - Keys are added to new objects with emplace(), which also replaces the basic_json::operator[] call for ordered_json. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
3351a8795b
commit
d1566fe9e8
2 files changed
+469
-264
No files matched your search
@@ -16,6 +16,7 @@
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // strtoull
|
||||
#include <cstring> // memchr, memcmp, memcpy
|
||||
#include <functional> // less
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
@@ -28,6 +29,7 @@
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/meta/void_t.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_escape.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
@@ -134,6 +136,26 @@ JSON_HEDLEY_ALWAYS_INLINE array_index_status parse_array_index(const StringType&
|
||||
return array_index_status::ok;
|
||||
}
|
||||
|
||||
/// whether two keys of type @a StringType are equivalent under @a Compare
|
||||
/// exactly if they are equal: std::less<StringType> and std::less<>
|
||||
template<typename Compare, typename StringType>
|
||||
struct is_less_comparator : std::false_type {};
|
||||
|
||||
template<typename StringType>
|
||||
struct is_less_comparator<std::less<StringType>, StringType> : std::true_type {};
|
||||
|
||||
template<typename StringType>
|
||||
struct is_less_comparator<std::less<void>, StringType> : std::true_type {};
|
||||
|
||||
/// whether the objects of type @a ObjectType find a key of type @a StringType
|
||||
/// exactly if it equals a stored key
|
||||
template<typename ObjectType, typename StringType, typename = void>
|
||||
struct finds_equal_keys : std::false_type {};
|
||||
|
||||
template<typename ObjectType, typename StringType>
|
||||
struct finds_equal_keys<ObjectType, StringType, void_t<typename ObjectType::key_compare, typename ObjectType::key_type>>
|
||||
: conjunction<is_less_comparator<typename ObjectType::key_compare, StringType>, std::is_same<typename ObjectType::key_type, StringType>> {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
@@ -589,62 +611,184 @@ class json_pointer
|
||||
return object_member_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief look up @a key in @a members without inserting it
|
||||
///
|
||||
/// Uses the same search as @ref object_member, so that both compile to the
|
||||
/// same code for std::map.
|
||||
/// @brief append a null value to @a array, like operator[] with the size of @a array
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& array_append(BasicJsonType& array)
|
||||
{
|
||||
auto& elements = *array.m_data.m_value.array;
|
||||
const auto old_capacity = array.container_capacity();
|
||||
elements.emplace_back();
|
||||
return array.set_parent_after_insert(elements.back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add @a key, which is missing, to @a object
|
||||
template<typename BasicJsonType>
|
||||
static auto object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace(key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
const auto old_capacity = object.container_capacity();
|
||||
const auto it = object.m_data.m_value.object->emplace(key, nullptr).first;
|
||||
return object.set_parent_after_insert(it->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return object.operator[](key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_add(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
return object_add_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief search @a key in @a members: the result is where the key is or,
|
||||
/// for objects that can insert at a position (std::map), where it belongs
|
||||
template<typename ObjectType>
|
||||
static auto object_find_impl(ObjectType& members, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(members.key_comp()(key, members.lower_bound(key)->first), members.lower_bound(key))
|
||||
{
|
||||
const auto it = members.lower_bound(key);
|
||||
return (it != members.end() && !members.key_comp()(key, it->first)) ? it : members.end();
|
||||
return members.lower_bound(key);
|
||||
}
|
||||
|
||||
template<typename ObjectType>
|
||||
static auto object_find_impl(ObjectType& members, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
-> decltype(members.find(key))
|
||||
{
|
||||
return members.find(key);
|
||||
}
|
||||
|
||||
template<typename ObjectType>
|
||||
static auto object_find(ObjectType& members, const string_t& key)
|
||||
-> decltype(object_find_impl(members, key, detail::priority_tag<1> {}))
|
||||
static auto object_search(ObjectType& members, const string_t& key)
|
||||
-> decltype(object_search_impl(members, key, detail::priority_tag<1> {}))
|
||||
{
|
||||
return object_find_impl(members, key, detail::priority_tag<1> {});
|
||||
return object_search_impl(members, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief whether @a it, the result of @ref object_search for @a key, points to @a key
|
||||
///
|
||||
/// With std::less, the key at the lower bound is equivalent to @a key
|
||||
/// exactly if it is equal to it, and comparing for equality is cheaper.
|
||||
template<typename ObjectType, typename Iterator,
|
||||
detail::enable_if_t<detail::finds_equal_keys<ObjectType, string_t>::value, int> = 0>
|
||||
static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<2> /*unused*/)
|
||||
-> decltype(members.lower_bound(key), it->first == key)
|
||||
{
|
||||
return it != members.end() && it->first == key;
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(members.key_comp()(key, it->first), members.lower_bound(key), bool())
|
||||
{
|
||||
return it != members.end() && !members.key_comp()(key, it->first);
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static bool object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& /*key*/, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return it != members.end();
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static bool object_search_found(ObjectType& members, const Iterator& it, const string_t& key)
|
||||
{
|
||||
return object_search_found_impl(members, it, key, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
/// @brief insert @a key, which is missing, into @a object at @a it, the
|
||||
/// result of @ref object_search for @a key
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static auto object_insert_impl(BasicJsonType& object, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace_hint(it, key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
const auto old_capacity = object.container_capacity();
|
||||
const auto pos = object.m_data.m_value.object->emplace_hint(it, key, nullptr);
|
||||
return object.set_parent_after_insert(pos->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static BasicJsonType& object_insert_impl(BasicJsonType& object, const Iterator& /*it*/, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return object_add(object, key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static BasicJsonType& object_insert(BasicJsonType& object, const Iterator& it, const string_t& key)
|
||||
{
|
||||
return object_insert_impl(object, it, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief undoes the change @ref get_unchecked_create made to an existing value
|
||||
|
||||
@ref get_unchecked walks existing values without changing them. At the
|
||||
first reference token it cannot resolve, @ref get_unchecked_create makes
|
||||
exactly one change to an existing value: it converts a null value, inserts
|
||||
a key into an object, or appends to an array. Everything it creates
|
||||
afterwards lies below that change. If a later step throws, the destructor
|
||||
of this guard reverts that one change, which removes everything created
|
||||
after it, so the value is left as it was (strong exception safety). The
|
||||
guard needs no try/catch and therefore also compiles with JSON_NOEXCEPTION.
|
||||
first reference token that does not select an existing value, @ref
|
||||
get_unchecked_create makes exactly one change to an existing value: it
|
||||
converts a null value, inserts a key into an object, or grows an array.
|
||||
Everything it creates afterwards lies below that change. If a later step
|
||||
throws, the destructor of this guard reverts that one change, which
|
||||
removes everything created after it, so the value is left as it was
|
||||
(strong exception safety). The guard needs no try/catch and therefore also
|
||||
compiles with JSON_NOEXCEPTION.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
class get_unchecked_rollback
|
||||
{
|
||||
public:
|
||||
get_unchecked_rollback() = default;
|
||||
/// record the change that resolving the reference token @a token makes to @a value
|
||||
get_unchecked_rollback(BasicJsonType* value, const string_t* token)
|
||||
: m_value(value)
|
||||
, m_key(token)
|
||||
{
|
||||
if (value->is_null())
|
||||
{
|
||||
// keep the null value (with its base class and positions) and
|
||||
// continue with a fresh null value in its place, which is what
|
||||
// assigning an array or object to it would have left there
|
||||
m_saved_null.swap(*value);
|
||||
m_change = change::null_converted;
|
||||
}
|
||||
else if (value->is_object())
|
||||
{
|
||||
m_change = change::key_inserted;
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(value->is_array());
|
||||
m_old_size = value->m_data.m_value.array->size();
|
||||
m_change = change::array_grown;
|
||||
}
|
||||
}
|
||||
|
||||
get_unchecked_rollback(const get_unchecked_rollback&) = delete;
|
||||
get_unchecked_rollback(get_unchecked_rollback&&) = delete;
|
||||
get_unchecked_rollback& operator=(const get_unchecked_rollback&) = delete;
|
||||
get_unchecked_rollback& operator=(get_unchecked_rollback&&) = delete;
|
||||
|
||||
~get_unchecked_rollback()
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_change != change::none))
|
||||
{
|
||||
undo();
|
||||
}
|
||||
}
|
||||
|
||||
/// keep the change
|
||||
void commit() noexcept
|
||||
{
|
||||
m_change = change::none;
|
||||
}
|
||||
|
||||
private:
|
||||
JSON_HEDLEY_NEVER_INLINE void undo() noexcept
|
||||
{
|
||||
switch (m_change)
|
||||
{
|
||||
case change::null_converted:
|
||||
// the copy of the null value also brings back its base
|
||||
// class and its positions
|
||||
*m_value = std::move(*m_saved_null);
|
||||
// the values created since go away with m_saved_null
|
||||
m_value->swap(m_saved_null);
|
||||
break;
|
||||
case change::key_inserted:
|
||||
m_value->m_data.m_value.object->erase(*m_key);
|
||||
@@ -658,37 +802,6 @@ class json_pointer
|
||||
}
|
||||
}
|
||||
|
||||
/// record that the null value @a value, of which @a saved is a copy, is about to be converted
|
||||
void null_converted(BasicJsonType* value, BasicJsonType* saved) noexcept
|
||||
{
|
||||
m_value = value;
|
||||
m_saved_null = saved;
|
||||
m_change = change::null_converted;
|
||||
}
|
||||
|
||||
/// record that @a key, a missing key, is about to be inserted into the object @a value
|
||||
void key_inserted(BasicJsonType* value, const string_t* key) noexcept
|
||||
{
|
||||
m_value = value;
|
||||
m_key = key;
|
||||
m_change = change::key_inserted;
|
||||
}
|
||||
|
||||
/// record that the array @a value is about to grow from @a old_size elements
|
||||
void array_grown(BasicJsonType* value, typename BasicJsonType::size_type old_size) noexcept
|
||||
{
|
||||
m_value = value;
|
||||
m_old_size = old_size;
|
||||
m_change = change::array_grown;
|
||||
}
|
||||
|
||||
/// keep all changes
|
||||
void commit() noexcept
|
||||
{
|
||||
m_change = change::none;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class change
|
||||
{
|
||||
none,
|
||||
@@ -697,11 +810,11 @@ class json_pointer
|
||||
array_grown
|
||||
};
|
||||
|
||||
change m_change = change::none;
|
||||
BasicJsonType* m_value = nullptr;
|
||||
const string_t* m_key = nullptr;
|
||||
BasicJsonType* m_value;
|
||||
const string_t* m_key;
|
||||
typename BasicJsonType::size_type m_old_size = 0;
|
||||
BasicJsonType* m_saved_null = nullptr;
|
||||
change m_change = change::none;
|
||||
BasicJsonType m_saved_null {};
|
||||
};
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
@@ -1087,11 +1200,11 @@ class json_pointer
|
||||
|
||||
Complexity: Linear in the length of the JSON pointer.
|
||||
|
||||
Exception safety: Strong guarantee. This function only walks existing
|
||||
values up to the last reference token. Inserting a key into an object or
|
||||
growing an array for the last reference token is strongly exception safe
|
||||
by itself; any other change is made by @ref get_unchecked_create, which
|
||||
undoes it if it throws.
|
||||
Exception safety: Strong guarantee. This function walks existing values
|
||||
until the first reference token that does not select one. If that is the
|
||||
last reference token, inserting the key into the object or growing the
|
||||
array is strongly exception safe by itself; any other change is made by
|
||||
@ref get_unchecked_create, which undoes it if a later step throws.
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
@@ -1100,28 +1213,31 @@ class json_pointer
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& get_unchecked(BasicJsonType* ptr) const
|
||||
{
|
||||
if (reference_tokens.empty())
|
||||
const auto* token = reference_tokens.data();
|
||||
const auto* const end = token + reference_tokens.size();
|
||||
if (token == end)
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// the null value is converted to an array or an object
|
||||
return get_unchecked_create(ptr, reference_tokens.data());
|
||||
return get_unchecked_create(ptr, token, end);
|
||||
}
|
||||
|
||||
// all reference tokens but the last one: walk existing values only
|
||||
const auto& last_token = reference_tokens.back();
|
||||
for (const auto* token = reference_tokens.data(); token != &last_token; ++token)
|
||||
// walk existing values; the walk itself inserts nothing, which keeps
|
||||
// the loop fast (insertions are done below or in get_unchecked_create)
|
||||
typename BasicJsonType::object_t::iterator it;
|
||||
do
|
||||
{
|
||||
if (ptr->is_object())
|
||||
{
|
||||
auto& members = *ptr->m_data.m_value.object;
|
||||
const auto it = object_find(members, *token);
|
||||
if (it == members.end())
|
||||
it = object_search(members, *token);
|
||||
if (!object_search_found(members, it, *token))
|
||||
{
|
||||
// the key is inserted
|
||||
return get_unchecked_create(ptr, token);
|
||||
// the key is inserted below or in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
ptr = &it->second;
|
||||
}
|
||||
@@ -1129,21 +1245,31 @@ class json_pointer
|
||||
{
|
||||
if (is_dash(*token))
|
||||
{
|
||||
// an element is appended
|
||||
return get_unchecked_create(ptr, token);
|
||||
if (token + 1 == end)
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end
|
||||
return array_append(*ptr);
|
||||
}
|
||||
// an element is appended in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
if (token + 1 == end)
|
||||
{
|
||||
// convert array index to number; unchecked access
|
||||
return ptr->operator[](array_index<BasicJsonType>(*token));
|
||||
}
|
||||
const auto idx = array_index<BasicJsonType>(*token);
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// the array is padded with null values
|
||||
return get_unchecked_create(ptr, token);
|
||||
// the array is padded in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else if (ptr->is_null())
|
||||
{
|
||||
// the null value is converted to an array or an object
|
||||
return get_unchecked_create(ptr, token);
|
||||
// the null value is converted in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1151,82 +1277,49 @@ class json_pointer
|
||||
throw_unresolved(*token, ptr);
|
||||
}
|
||||
}
|
||||
while (++token != end);
|
||||
|
||||
// the last reference token: insert a missing key or grow the array
|
||||
if (ptr->is_object())
|
||||
if (token == end)
|
||||
{
|
||||
// use unchecked object access
|
||||
return object_member(*ptr, last_token);
|
||||
return *ptr;
|
||||
}
|
||||
if (ptr->is_array())
|
||||
if (token + 1 == end && ptr->is_object())
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end; otherwise convert
|
||||
// array index to number; unchecked access
|
||||
const auto idx = is_dash(last_token)
|
||||
? ptr->m_data.m_value.array->size()
|
||||
: array_index<BasicJsonType>(last_token);
|
||||
return ptr->operator[](idx);
|
||||
// insert the missing key where the walk found it belongs
|
||||
return object_insert(*ptr, it, *token);
|
||||
}
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// the null value is converted to an array or an object
|
||||
return get_unchecked_create(ptr, &last_token);
|
||||
}
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(last_token, ptr);
|
||||
return get_unchecked_create(ptr, token, end);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief create the values for the reference tokens from @a first on
|
||||
|
||||
Continues @ref get_unchecked at the first reference token that does not
|
||||
select an existing value. That token changes @a ptr, an existing value
|
||||
(see @ref get_unchecked_rollback); all later tokens only change values
|
||||
created here.
|
||||
select an existing value. That token changes @a ptr, an existing value (see
|
||||
@ref get_unchecked_rollback); all later tokens only change values created
|
||||
here.
|
||||
|
||||
@param[in] ptr the value the reference token @a first is applied to
|
||||
@param[in] first the first reference token to resolve (an element of
|
||||
@ref reference_tokens)
|
||||
@param[in] end the end of @ref reference_tokens
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
@throw out_of_range.404 if the JSON pointer can not be resolved
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NEVER_INLINE BasicJsonType& get_unchecked_create(BasicJsonType* ptr, const string_t* first) const
|
||||
JSON_HEDLEY_NEVER_INLINE static BasicJsonType& get_unchecked_create(BasicJsonType* ptr, const string_t* first, const string_t* end)
|
||||
{
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// the copy keeps the base class and the positions of the null
|
||||
// value; it must outlive the guard
|
||||
BasicJsonType saved_null = *ptr;
|
||||
get_unchecked_rollback<BasicJsonType> rollback;
|
||||
rollback.null_converted(ptr, &saved_null);
|
||||
BasicJsonType& result = create_values(ptr, first);
|
||||
rollback.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
get_unchecked_rollback<BasicJsonType> rollback;
|
||||
if (ptr->is_object())
|
||||
{
|
||||
rollback.key_inserted(ptr, first);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(ptr->is_array());
|
||||
rollback.array_grown(ptr, ptr->m_data.m_value.array->size());
|
||||
}
|
||||
BasicJsonType& result = create_values(ptr, first);
|
||||
get_unchecked_rollback<BasicJsonType> rollback(ptr, first);
|
||||
BasicJsonType& result = create_values(ptr, first, end);
|
||||
rollback.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// the loop of @ref get_unchecked_create
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& create_values(BasicJsonType* ptr, const string_t* first) const
|
||||
static BasicJsonType& create_values(BasicJsonType* ptr, const string_t* first, const string_t* end)
|
||||
{
|
||||
const auto* const end = reference_tokens.data() + reference_tokens.size();
|
||||
for (const auto* token = first; token != end; ++token)
|
||||
{
|
||||
const auto& reference_token = *token;
|
||||
@@ -1246,16 +1339,25 @@ class json_pointer
|
||||
return std::isdigit(x);
|
||||
});
|
||||
|
||||
// change value to an array for array indices or "-" or to object otherwise
|
||||
*ptr = (nums || reference_token == "-")
|
||||
? detail::value_t::array
|
||||
: detail::value_t::object;
|
||||
// change value to an array for array indices or "-" or to an
|
||||
// object otherwise; every null value here is a fresh one (see
|
||||
// get_unchecked_rollback), so converting it in place leaves
|
||||
// the same value as assigning an array or object to it
|
||||
if (nums || is_dash(reference_token))
|
||||
{
|
||||
ptr->template convert_null_to<typename BasicJsonType::array_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr->template convert_null_to<typename BasicJsonType::object_t>();
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr->is_object())
|
||||
{
|
||||
// use unchecked object access
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
// the key is missing: the object is new, or get_unchecked did
|
||||
// not find the key
|
||||
ptr = &object_add(*ptr, reference_token);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1265,7 +1367,7 @@ class json_pointer
|
||||
if (is_dash(reference_token))
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end
|
||||
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
|
||||
ptr = &array_append(*ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Generated
+235
-132
@@ -23593,6 +23593,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // strtoull
|
||||
#include <cstring> // memchr, memcmp, memcpy
|
||||
#include <functional> // less
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
@@ -23609,6 +23610,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/meta/type_traits.hpp>
|
||||
|
||||
// #include <nlohmann/detail/meta/void_t.hpp>
|
||||
|
||||
// #include <nlohmann/detail/string_concat.hpp>
|
||||
|
||||
// #include <nlohmann/detail/string_escape.hpp>
|
||||
@@ -23719,6 +23722,26 @@ JSON_HEDLEY_ALWAYS_INLINE array_index_status parse_array_index(const StringType&
|
||||
return array_index_status::ok;
|
||||
}
|
||||
|
||||
/// whether two keys of type @a StringType are equivalent under @a Compare
|
||||
/// exactly if they are equal: std::less<StringType> and std::less<>
|
||||
template<typename Compare, typename StringType>
|
||||
struct is_less_comparator : std::false_type {};
|
||||
|
||||
template<typename StringType>
|
||||
struct is_less_comparator<std::less<StringType>, StringType> : std::true_type {};
|
||||
|
||||
template<typename StringType>
|
||||
struct is_less_comparator<std::less<void>, StringType> : std::true_type {};
|
||||
|
||||
/// whether the objects of type @a ObjectType find a key of type @a StringType
|
||||
/// exactly if it equals a stored key
|
||||
template<typename ObjectType, typename StringType, typename = void>
|
||||
struct finds_equal_keys : std::false_type {};
|
||||
|
||||
template<typename ObjectType, typename StringType>
|
||||
struct finds_equal_keys<ObjectType, StringType, void_t<typename ObjectType::key_compare, typename ObjectType::key_type>>
|
||||
: conjunction<is_less_comparator<typename ObjectType::key_compare, StringType>, std::is_same<typename ObjectType::key_type, StringType>> {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
@@ -24174,62 +24197,184 @@ class json_pointer
|
||||
return object_member_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief look up @a key in @a members without inserting it
|
||||
///
|
||||
/// Uses the same search as @ref object_member, so that both compile to the
|
||||
/// same code for std::map.
|
||||
/// @brief append a null value to @a array, like operator[] with the size of @a array
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& array_append(BasicJsonType& array)
|
||||
{
|
||||
auto& elements = *array.m_data.m_value.array;
|
||||
const auto old_capacity = array.container_capacity();
|
||||
elements.emplace_back();
|
||||
return array.set_parent_after_insert(elements.back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add @a key, which is missing, to @a object
|
||||
template<typename BasicJsonType>
|
||||
static auto object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace(key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
const auto old_capacity = object.container_capacity();
|
||||
const auto it = object.m_data.m_value.object->emplace(key, nullptr).first;
|
||||
return object.set_parent_after_insert(it->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return object.operator[](key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_add(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
return object_add_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief search @a key in @a members: the result is where the key is or,
|
||||
/// for objects that can insert at a position (std::map), where it belongs
|
||||
template<typename ObjectType>
|
||||
static auto object_find_impl(ObjectType& members, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(members.key_comp()(key, members.lower_bound(key)->first), members.lower_bound(key))
|
||||
{
|
||||
const auto it = members.lower_bound(key);
|
||||
return (it != members.end() && !members.key_comp()(key, it->first)) ? it : members.end();
|
||||
return members.lower_bound(key);
|
||||
}
|
||||
|
||||
template<typename ObjectType>
|
||||
static auto object_find_impl(ObjectType& members, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
-> decltype(members.find(key))
|
||||
{
|
||||
return members.find(key);
|
||||
}
|
||||
|
||||
template<typename ObjectType>
|
||||
static auto object_find(ObjectType& members, const string_t& key)
|
||||
-> decltype(object_find_impl(members, key, detail::priority_tag<1> {}))
|
||||
static auto object_search(ObjectType& members, const string_t& key)
|
||||
-> decltype(object_search_impl(members, key, detail::priority_tag<1> {}))
|
||||
{
|
||||
return object_find_impl(members, key, detail::priority_tag<1> {});
|
||||
return object_search_impl(members, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief whether @a it, the result of @ref object_search for @a key, points to @a key
|
||||
///
|
||||
/// With std::less, the key at the lower bound is equivalent to @a key
|
||||
/// exactly if it is equal to it, and comparing for equality is cheaper.
|
||||
template<typename ObjectType, typename Iterator,
|
||||
detail::enable_if_t<detail::finds_equal_keys<ObjectType, string_t>::value, int> = 0>
|
||||
static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<2> /*unused*/)
|
||||
-> decltype(members.lower_bound(key), it->first == key)
|
||||
{
|
||||
return it != members.end() && it->first == key;
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(members.key_comp()(key, it->first), members.lower_bound(key), bool())
|
||||
{
|
||||
return it != members.end() && !members.key_comp()(key, it->first);
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static bool object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& /*key*/, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return it != members.end();
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static bool object_search_found(ObjectType& members, const Iterator& it, const string_t& key)
|
||||
{
|
||||
return object_search_found_impl(members, it, key, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
/// @brief insert @a key, which is missing, into @a object at @a it, the
|
||||
/// result of @ref object_search for @a key
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static auto object_insert_impl(BasicJsonType& object, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace_hint(it, key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
const auto old_capacity = object.container_capacity();
|
||||
const auto pos = object.m_data.m_value.object->emplace_hint(it, key, nullptr);
|
||||
return object.set_parent_after_insert(pos->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static BasicJsonType& object_insert_impl(BasicJsonType& object, const Iterator& /*it*/, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return object_add(object, key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static BasicJsonType& object_insert(BasicJsonType& object, const Iterator& it, const string_t& key)
|
||||
{
|
||||
return object_insert_impl(object, it, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief undoes the change @ref get_unchecked_create made to an existing value
|
||||
|
||||
@ref get_unchecked walks existing values without changing them. At the
|
||||
first reference token it cannot resolve, @ref get_unchecked_create makes
|
||||
exactly one change to an existing value: it converts a null value, inserts
|
||||
a key into an object, or appends to an array. Everything it creates
|
||||
afterwards lies below that change. If a later step throws, the destructor
|
||||
of this guard reverts that one change, which removes everything created
|
||||
after it, so the value is left as it was (strong exception safety). The
|
||||
guard needs no try/catch and therefore also compiles with JSON_NOEXCEPTION.
|
||||
first reference token that does not select an existing value, @ref
|
||||
get_unchecked_create makes exactly one change to an existing value: it
|
||||
converts a null value, inserts a key into an object, or grows an array.
|
||||
Everything it creates afterwards lies below that change. If a later step
|
||||
throws, the destructor of this guard reverts that one change, which
|
||||
removes everything created after it, so the value is left as it was
|
||||
(strong exception safety). The guard needs no try/catch and therefore also
|
||||
compiles with JSON_NOEXCEPTION.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
class get_unchecked_rollback
|
||||
{
|
||||
public:
|
||||
get_unchecked_rollback() = default;
|
||||
/// record the change that resolving the reference token @a token makes to @a value
|
||||
get_unchecked_rollback(BasicJsonType* value, const string_t* token)
|
||||
: m_value(value)
|
||||
, m_key(token)
|
||||
{
|
||||
if (value->is_null())
|
||||
{
|
||||
// keep the null value (with its base class and positions) and
|
||||
// continue with a fresh null value in its place, which is what
|
||||
// assigning an array or object to it would have left there
|
||||
m_saved_null.swap(*value);
|
||||
m_change = change::null_converted;
|
||||
}
|
||||
else if (value->is_object())
|
||||
{
|
||||
m_change = change::key_inserted;
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(value->is_array());
|
||||
m_old_size = value->m_data.m_value.array->size();
|
||||
m_change = change::array_grown;
|
||||
}
|
||||
}
|
||||
|
||||
get_unchecked_rollback(const get_unchecked_rollback&) = delete;
|
||||
get_unchecked_rollback(get_unchecked_rollback&&) = delete;
|
||||
get_unchecked_rollback& operator=(const get_unchecked_rollback&) = delete;
|
||||
get_unchecked_rollback& operator=(get_unchecked_rollback&&) = delete;
|
||||
|
||||
~get_unchecked_rollback()
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_change != change::none))
|
||||
{
|
||||
undo();
|
||||
}
|
||||
}
|
||||
|
||||
/// keep the change
|
||||
void commit() noexcept
|
||||
{
|
||||
m_change = change::none;
|
||||
}
|
||||
|
||||
private:
|
||||
JSON_HEDLEY_NEVER_INLINE void undo() noexcept
|
||||
{
|
||||
switch (m_change)
|
||||
{
|
||||
case change::null_converted:
|
||||
// the copy of the null value also brings back its base
|
||||
// class and its positions
|
||||
*m_value = std::move(*m_saved_null);
|
||||
// the values created since go away with m_saved_null
|
||||
m_value->swap(m_saved_null);
|
||||
break;
|
||||
case change::key_inserted:
|
||||
m_value->m_data.m_value.object->erase(*m_key);
|
||||
@@ -24243,37 +24388,6 @@ class json_pointer
|
||||
}
|
||||
}
|
||||
|
||||
/// record that the null value @a value, of which @a saved is a copy, is about to be converted
|
||||
void null_converted(BasicJsonType* value, BasicJsonType* saved) noexcept
|
||||
{
|
||||
m_value = value;
|
||||
m_saved_null = saved;
|
||||
m_change = change::null_converted;
|
||||
}
|
||||
|
||||
/// record that @a key, a missing key, is about to be inserted into the object @a value
|
||||
void key_inserted(BasicJsonType* value, const string_t* key) noexcept
|
||||
{
|
||||
m_value = value;
|
||||
m_key = key;
|
||||
m_change = change::key_inserted;
|
||||
}
|
||||
|
||||
/// record that the array @a value is about to grow from @a old_size elements
|
||||
void array_grown(BasicJsonType* value, typename BasicJsonType::size_type old_size) noexcept
|
||||
{
|
||||
m_value = value;
|
||||
m_old_size = old_size;
|
||||
m_change = change::array_grown;
|
||||
}
|
||||
|
||||
/// keep all changes
|
||||
void commit() noexcept
|
||||
{
|
||||
m_change = change::none;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class change
|
||||
{
|
||||
none,
|
||||
@@ -24282,11 +24396,11 @@ class json_pointer
|
||||
array_grown
|
||||
};
|
||||
|
||||
change m_change = change::none;
|
||||
BasicJsonType* m_value = nullptr;
|
||||
const string_t* m_key = nullptr;
|
||||
BasicJsonType* m_value;
|
||||
const string_t* m_key;
|
||||
typename BasicJsonType::size_type m_old_size = 0;
|
||||
BasicJsonType* m_saved_null = nullptr;
|
||||
change m_change = change::none;
|
||||
BasicJsonType m_saved_null {};
|
||||
};
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
@@ -24672,11 +24786,11 @@ class json_pointer
|
||||
|
||||
Complexity: Linear in the length of the JSON pointer.
|
||||
|
||||
Exception safety: Strong guarantee. This function only walks existing
|
||||
values up to the last reference token. Inserting a key into an object or
|
||||
growing an array for the last reference token is strongly exception safe
|
||||
by itself; any other change is made by @ref get_unchecked_create, which
|
||||
undoes it if it throws.
|
||||
Exception safety: Strong guarantee. This function walks existing values
|
||||
until the first reference token that does not select one. If that is the
|
||||
last reference token, inserting the key into the object or growing the
|
||||
array is strongly exception safe by itself; any other change is made by
|
||||
@ref get_unchecked_create, which undoes it if a later step throws.
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
@@ -24685,28 +24799,31 @@ class json_pointer
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& get_unchecked(BasicJsonType* ptr) const
|
||||
{
|
||||
if (reference_tokens.empty())
|
||||
const auto* token = reference_tokens.data();
|
||||
const auto* const end = token + reference_tokens.size();
|
||||
if (token == end)
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// the null value is converted to an array or an object
|
||||
return get_unchecked_create(ptr, reference_tokens.data());
|
||||
return get_unchecked_create(ptr, token, end);
|
||||
}
|
||||
|
||||
// all reference tokens but the last one: walk existing values only
|
||||
const auto& last_token = reference_tokens.back();
|
||||
for (const auto* token = reference_tokens.data(); token != &last_token; ++token)
|
||||
// walk existing values; the walk itself inserts nothing, which keeps
|
||||
// the loop fast (insertions are done below or in get_unchecked_create)
|
||||
typename BasicJsonType::object_t::iterator it;
|
||||
do
|
||||
{
|
||||
if (ptr->is_object())
|
||||
{
|
||||
auto& members = *ptr->m_data.m_value.object;
|
||||
const auto it = object_find(members, *token);
|
||||
if (it == members.end())
|
||||
it = object_search(members, *token);
|
||||
if (!object_search_found(members, it, *token))
|
||||
{
|
||||
// the key is inserted
|
||||
return get_unchecked_create(ptr, token);
|
||||
// the key is inserted below or in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
ptr = &it->second;
|
||||
}
|
||||
@@ -24714,21 +24831,31 @@ class json_pointer
|
||||
{
|
||||
if (is_dash(*token))
|
||||
{
|
||||
// an element is appended
|
||||
return get_unchecked_create(ptr, token);
|
||||
if (token + 1 == end)
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end
|
||||
return array_append(*ptr);
|
||||
}
|
||||
// an element is appended in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
if (token + 1 == end)
|
||||
{
|
||||
// convert array index to number; unchecked access
|
||||
return ptr->operator[](array_index<BasicJsonType>(*token));
|
||||
}
|
||||
const auto idx = array_index<BasicJsonType>(*token);
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// the array is padded with null values
|
||||
return get_unchecked_create(ptr, token);
|
||||
// the array is padded in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else if (ptr->is_null())
|
||||
{
|
||||
// the null value is converted to an array or an object
|
||||
return get_unchecked_create(ptr, token);
|
||||
// the null value is converted in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -24736,82 +24863,49 @@ class json_pointer
|
||||
throw_unresolved(*token, ptr);
|
||||
}
|
||||
}
|
||||
while (++token != end);
|
||||
|
||||
// the last reference token: insert a missing key or grow the array
|
||||
if (ptr->is_object())
|
||||
if (token == end)
|
||||
{
|
||||
// use unchecked object access
|
||||
return object_member(*ptr, last_token);
|
||||
return *ptr;
|
||||
}
|
||||
if (ptr->is_array())
|
||||
if (token + 1 == end && ptr->is_object())
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end; otherwise convert
|
||||
// array index to number; unchecked access
|
||||
const auto idx = is_dash(last_token)
|
||||
? ptr->m_data.m_value.array->size()
|
||||
: array_index<BasicJsonType>(last_token);
|
||||
return ptr->operator[](idx);
|
||||
// insert the missing key where the walk found it belongs
|
||||
return object_insert(*ptr, it, *token);
|
||||
}
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// the null value is converted to an array or an object
|
||||
return get_unchecked_create(ptr, &last_token);
|
||||
}
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(last_token, ptr);
|
||||
return get_unchecked_create(ptr, token, end);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief create the values for the reference tokens from @a first on
|
||||
|
||||
Continues @ref get_unchecked at the first reference token that does not
|
||||
select an existing value. That token changes @a ptr, an existing value
|
||||
(see @ref get_unchecked_rollback); all later tokens only change values
|
||||
created here.
|
||||
select an existing value. That token changes @a ptr, an existing value (see
|
||||
@ref get_unchecked_rollback); all later tokens only change values created
|
||||
here.
|
||||
|
||||
@param[in] ptr the value the reference token @a first is applied to
|
||||
@param[in] first the first reference token to resolve (an element of
|
||||
@ref reference_tokens)
|
||||
@param[in] end the end of @ref reference_tokens
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
@throw out_of_range.404 if the JSON pointer can not be resolved
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NEVER_INLINE BasicJsonType& get_unchecked_create(BasicJsonType* ptr, const string_t* first) const
|
||||
JSON_HEDLEY_NEVER_INLINE static BasicJsonType& get_unchecked_create(BasicJsonType* ptr, const string_t* first, const string_t* end)
|
||||
{
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// the copy keeps the base class and the positions of the null
|
||||
// value; it must outlive the guard
|
||||
BasicJsonType saved_null = *ptr;
|
||||
get_unchecked_rollback<BasicJsonType> rollback;
|
||||
rollback.null_converted(ptr, &saved_null);
|
||||
BasicJsonType& result = create_values(ptr, first);
|
||||
rollback.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
get_unchecked_rollback<BasicJsonType> rollback;
|
||||
if (ptr->is_object())
|
||||
{
|
||||
rollback.key_inserted(ptr, first);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(ptr->is_array());
|
||||
rollback.array_grown(ptr, ptr->m_data.m_value.array->size());
|
||||
}
|
||||
BasicJsonType& result = create_values(ptr, first);
|
||||
get_unchecked_rollback<BasicJsonType> rollback(ptr, first);
|
||||
BasicJsonType& result = create_values(ptr, first, end);
|
||||
rollback.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// the loop of @ref get_unchecked_create
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& create_values(BasicJsonType* ptr, const string_t* first) const
|
||||
static BasicJsonType& create_values(BasicJsonType* ptr, const string_t* first, const string_t* end)
|
||||
{
|
||||
const auto* const end = reference_tokens.data() + reference_tokens.size();
|
||||
for (const auto* token = first; token != end; ++token)
|
||||
{
|
||||
const auto& reference_token = *token;
|
||||
@@ -24831,16 +24925,25 @@ class json_pointer
|
||||
return std::isdigit(x);
|
||||
});
|
||||
|
||||
// change value to an array for array indices or "-" or to object otherwise
|
||||
*ptr = (nums || reference_token == "-")
|
||||
? detail::value_t::array
|
||||
: detail::value_t::object;
|
||||
// change value to an array for array indices or "-" or to an
|
||||
// object otherwise; every null value here is a fresh one (see
|
||||
// get_unchecked_rollback), so converting it in place leaves
|
||||
// the same value as assigning an array or object to it
|
||||
if (nums || is_dash(reference_token))
|
||||
{
|
||||
ptr->template convert_null_to<typename BasicJsonType::array_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr->template convert_null_to<typename BasicJsonType::object_t>();
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr->is_object())
|
||||
{
|
||||
// use unchecked object access
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
// the key is missing: the object is new, or get_unchecked did
|
||||
// not find the key
|
||||
ptr = &object_add(*ptr, reference_token);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -24850,7 +24953,7 @@ class json_pointer
|
||||
if (is_dash(reference_token))
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end
|
||||
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
|
||||
ptr = &array_append(*ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user