mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 10:47:13 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82250cbe02 | ||
|
|
8b2d29f28b |
No files matched your search
@@ -294,40 +294,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
{
|
return *m_it.array_iterator;
|
||||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
|
||||||
return m_it.object_iterator->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
|
||||||
return *m_it.array_iterator;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
throw_cannot_get_value();
|
|
||||||
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
|
||||||
{
|
|
||||||
return *m_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw_cannot_get_value();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||||
|
return m_it.object_iterator->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value (null has no value to point to)
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin()))
|
||||||
|
{
|
||||||
|
return *m_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_cannot_get_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -338,38 +323,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
{
|
return &*m_it.array_iterator;
|
||||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
|
||||||
return &(m_it.object_iterator->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
|
||||||
return &*m_it.array_iterator;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
|
||||||
{
|
|
||||||
return m_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw_cannot_get_value();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||||
|
return &(m_it.object_iterator->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
||||||
|
{
|
||||||
|
return m_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_cannot_get_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -391,35 +363,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
++m_it.array_iterator;
|
||||||
{
|
return *this;
|
||||||
std::advance(m_it.object_iterator, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
std::advance(m_it.array_iterator, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
++m_it.primitive_iterator;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
std::advance(m_it.object_iterator, 1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
++m_it.primitive_iterator;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,35 +399,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
--m_it.array_iterator;
|
||||||
{
|
return *this;
|
||||||
std::advance(m_it.object_iterator, -1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
std::advance(m_it.array_iterator, -1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
--m_it.primitive_iterator;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
std::advance(m_it.object_iterator, -1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
--m_it.primitive_iterator;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,25 +435,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
return (m_it.array_iterator == other.m_it.array_iterator);
|
||||||
return (m_it.object_iterator == other.m_it.object_iterator);
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return (m_it.array_iterator == other.m_it.array_iterator);
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
return (m_it.object_iterator == other.m_it.object_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
+221
-125
@@ -734,6 +734,34 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// true if no child of the non-empty array/object v has children of
|
||||||
|
// its own, i.e. v can be freed in one go (its destructor then
|
||||||
|
// destroys scalars and empty containers only, so it recurses at most
|
||||||
|
// one level deep); stops at the first structured child it meets
|
||||||
|
static bool all_children_leaves(const basic_json& v) noexcept
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
const auto& a = *v.m_data.m_value.array;
|
||||||
|
for (auto it = a.rbegin(); it != a.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (!has_no_children(*it))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const auto& el : *v.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
if (!has_no_children(el.second))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// The walk in destroy_container() may take the children of a
|
// The walk in destroy_container() may take the children of a
|
||||||
// container in any order, as long as it picks the same child again
|
// container in any order, as long as it picks the same child again
|
||||||
// while that container is not modified in between. Arrays and
|
// while that container is not modified in between. Arrays and
|
||||||
@@ -789,15 +817,16 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deallocates the (already empty) array/object held by v; this is
|
// deallocates the array/object held by v, which is either empty or
|
||||||
// the same allocator-based free the old recursive implementation
|
// holds only scalars and empty containers (so its destructor
|
||||||
// used, just factored out so every level of the walk in destroy()
|
// recurses at most one level deep); this is the same allocator-based
|
||||||
// can share it
|
// free the old recursive implementation used, just factored out so
|
||||||
|
// every level of the walk in destroy() can share it
|
||||||
static void free_container(basic_json& v) noexcept
|
static void free_container(basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
|
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
|
||||||
AllocatorType<array_t> alloc;
|
AllocatorType<array_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||||
@@ -805,7 +834,6 @@ private:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
|
||||||
AllocatorType<object_t> alloc;
|
AllocatorType<object_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||||
@@ -850,7 +878,58 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy the tree without recursing per nesting level and
|
// This json_value is not itself a basic_json, so the top-level
|
||||||
|
// container is first moved into a local stand-in ("cur"); a
|
||||||
|
// default-constructed basic_json has a null pointer in its
|
||||||
|
// m_value (see data::m_value's initializer), so swapping it
|
||||||
|
// with *this leaves this union's own pointer null, and it is
|
||||||
|
// never looked at or freed a second time.
|
||||||
|
basic_json cur;
|
||||||
|
cur.m_data.m_type = t;
|
||||||
|
using std::swap;
|
||||||
|
swap(cur.m_data.m_value, *this);
|
||||||
|
|
||||||
|
destroy_bounded(cur, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the array/object v, whose children are destroyed first.
|
||||||
|
// The first recursion_depth_limit() levels are destroyed by plain
|
||||||
|
// recursion, which is the fastest way and cannot throw; anything
|
||||||
|
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
||||||
|
static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
|
{
|
||||||
|
destroy_walk(v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
for (auto& child : *v.m_data.m_value.array)
|
||||||
|
{
|
||||||
|
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
||||||
|
{
|
||||||
|
destroy_bounded(child, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto& child : *v.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
||||||
|
{
|
||||||
|
destroy_bounded(child.second, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only leaves and already destroyed (null) children remain
|
||||||
|
free_container(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the tree below top without recursing per nesting level and
|
||||||
// without any heap allocation: a heap-allocated flattening
|
// without any heap allocation: a heap-allocated flattening
|
||||||
// stack (the previous implementation) can itself throw
|
// stack (the previous implementation) can itself throw
|
||||||
// bad_alloc, which would escape this noexcept destructor and
|
// bad_alloc, which would escape this noexcept destructor and
|
||||||
@@ -874,16 +953,24 @@ public:
|
|||||||
// pointer in its m_value (see data::m_value's initializer),
|
// pointer in its m_value (see data::m_value's initializer),
|
||||||
// so swapping it with *this leaves this union's own pointer
|
// so swapping it with *this leaves this union's own pointer
|
||||||
// null, and it is never looked at or freed a second time.
|
// null, and it is never looked at or freed a second time.
|
||||||
|
static void destroy_walk(basic_json& top) noexcept
|
||||||
|
{
|
||||||
basic_json cur;
|
basic_json cur;
|
||||||
cur.m_data.m_type = t;
|
take(cur, top);
|
||||||
using std::swap;
|
|
||||||
swap(cur.m_data.m_value, *this);
|
|
||||||
|
|
||||||
basic_json prev; // value_t::null: no parent
|
basic_json prev; // value_t::null: no parent
|
||||||
|
|
||||||
|
// true right after cur was entered: its children have not been
|
||||||
|
// looked at yet. If none of them has children of its own, cur is
|
||||||
|
// freed in one go instead of child by child; this is tried once
|
||||||
|
// per container, so the walk stays linear.
|
||||||
|
bool fresh = true;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (has_no_children(cur))
|
const bool fresh_now = fresh;
|
||||||
|
fresh = false;
|
||||||
|
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
||||||
{
|
{
|
||||||
if (prev.m_data.m_type == value_t::null)
|
if (prev.m_data.m_type == value_t::null)
|
||||||
{
|
{
|
||||||
@@ -922,6 +1009,7 @@ public:
|
|||||||
take(cur_child_ref, prev);
|
take(cur_child_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
take(cur, tmp);
|
take(cur, tmp);
|
||||||
|
fresh = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7137,12 +7225,11 @@ public:
|
|||||||
std::size_t index = 0;
|
std::size_t index = 0;
|
||||||
/// objects: the next member of source to look at
|
/// objects: the next member of source to look at
|
||||||
const_iterator member{}; // NOLINT(readability-redundant-member-init)
|
const_iterator member{}; // NOLINT(readability-redundant-member-init)
|
||||||
/// objects: the keys common to both, in source's order
|
/// objects: for each member of source (in order), its value in
|
||||||
std::vector<typename object_t::key_type> common_keys{}; // NOLINT(readability-redundant-member-init)
|
/// target, or nullptr if target has no such key; `index` is the next
|
||||||
/// objects: the next entry of common_keys
|
std::vector<const basic_json*> target_values{}; // NOLINT(readability-redundant-member-init)
|
||||||
std::size_t next_common = 0;
|
/// objects: the members only target has, in target's order
|
||||||
/// objects: the "add" operations for keys only target has
|
std::vector<const_iterator> added{}; // NOLINT(readability-redundant-member-init)
|
||||||
basic_json added_ops{}; // NOLINT(readability-redundant-member-init)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The operations of a diff are built by the functions below rather than
|
// The operations of a diff are built by the functions below rather than
|
||||||
@@ -7198,111 +7285,113 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief appends the "add" operations for the members @a added to @a result
|
||||||
|
static void diff_added(basic_json& result, const std::vector<const_iterator>& added, const string_t& path)
|
||||||
|
{
|
||||||
|
for (const auto& it : added)
|
||||||
|
{
|
||||||
|
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief compare the keys of objects @a source and @a target
|
@brief compare the keys of objects @a source and @a target
|
||||||
|
|
||||||
If object_t does not keep its members in insertion order, or if the keys
|
If object_t does not keep its members in insertion order, or if the keys
|
||||||
both objects have are in the same order in both, and the keys only
|
both objects have are in the same order in both, and the keys only
|
||||||
@a target has come after them, stores the keys common to both in
|
@a target has come after them, stores in @a target_values, for each member
|
||||||
source's order in @a common_keys, stores the "add" operations for the keys
|
of source in order, its value in target (nullptr for a key target does not
|
||||||
only @a target has in @a added_ops, and returns true: the caller then diffs
|
have), stores the members only target has in @a added, and returns true:
|
||||||
the objects member by member. Otherwise, appends operations that remove
|
the caller then diffs the objects member by member. Otherwise, appends
|
||||||
every member of @a source and add every member of @a target to @a result,
|
operations that remove every member of @a source and add every member of
|
||||||
and returns false.
|
@a target to @a result, and returns false.
|
||||||
*/
|
*/
|
||||||
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
const string_t& path, std::vector<typename object_t::key_type>& common_keys,
|
const string_t& path, std::vector<const basic_json*>& target_values,
|
||||||
basic_json& added_ops)
|
std::vector<const_iterator>& added)
|
||||||
{
|
{
|
||||||
// first pass: record, for every source key, whether it is
|
// first pass: one lookup per source key
|
||||||
// common to both objects (in source's iteration order) or
|
target_values.reserve(source.size());
|
||||||
// was deleted (i.e., in source but not in target) -- this is
|
std::size_t common = 0;
|
||||||
// a by-product of the target.find() call already needed to
|
|
||||||
// tell the two cases apart, so it adds no extra lookups. The
|
|
||||||
// "remove" ops themselves are emitted later, interleaved
|
|
||||||
// with the per-key diffs in the caller's fast path, to match
|
|
||||||
// source's original iteration order (as the original,
|
|
||||||
// pre-reordering-aware implementation did) instead of
|
|
||||||
// grouping all removes before all per-key diffs.
|
|
||||||
std::vector<typename object_t::key_type> common_keys_source_order;
|
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
{
|
{
|
||||||
if (target.find(it.key()) != target.end())
|
const auto found = target.find(it.key());
|
||||||
|
if (found != target.cend())
|
||||||
{
|
{
|
||||||
common_keys_source_order.push_back(it.key());
|
target_values.push_back(&found.value());
|
||||||
}
|
++common;
|
||||||
}
|
|
||||||
|
|
||||||
// second pass: find keys that were added (i.e., in target but
|
|
||||||
// not in source), and record the keys common to both, in
|
|
||||||
// target's iteration order -- again a by-product of the
|
|
||||||
// source.find() call already needed to detect added keys. At
|
|
||||||
// the same time, determine whether every added key comes
|
|
||||||
// after every common key in target's order (a precondition
|
|
||||||
// for the fast path, which only ever appends new keys
|
|
||||||
// at the very end). Both are only needed for an object_t that
|
|
||||||
// keeps its members in insertion order, such as the one
|
|
||||||
// backing `ordered_json`; for any other object_t, the fast
|
|
||||||
// path is always taken and they are not computed.
|
|
||||||
// The patch ops for keys that were added (i.e., in target but not
|
|
||||||
// in source) are built here so the fast path can reuse
|
|
||||||
// them without a second source.find() per target key. Only
|
|
||||||
// used by the fast path -- the slow (reordering) path
|
|
||||||
// rebuilds "add" ops for every key itself.
|
|
||||||
std::vector<typename object_t::key_type> common_keys_target_order;
|
|
||||||
bool new_keys_form_suffix = true;
|
|
||||||
bool seen_new_key = false;
|
|
||||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
|
||||||
{
|
|
||||||
if (source.find(it.key()) == source.end())
|
|
||||||
{
|
|
||||||
seen_new_key = true;
|
|
||||||
diff_add(added_ops, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
target_values.push_back(nullptr);
|
||||||
#pragma warning(push )
|
|
||||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
|
||||||
#endif
|
|
||||||
if (detail::is_ordered_map<object_t>::value)
|
|
||||||
{
|
|
||||||
common_keys_target_order.push_back(it.key());
|
|
||||||
if (seen_new_key)
|
|
||||||
{
|
|
||||||
new_keys_form_suffix = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
|
||||||
#pragma warning( pop )
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only an object type that keeps its members in insertion
|
// second pass: find the keys only target has. An object type that
|
||||||
// order, such as nlohmann::ordered_map, can need reordering:
|
// keeps its members in insertion order, such as the one backing
|
||||||
// patch() appends a new member at the end of such an object.
|
// ordered_json, also needs the common keys to be in the same order
|
||||||
// Any other object type places its members itself - std::map
|
// in both objects, and the new keys to come after them, as patch()
|
||||||
// in key order, a hash map in an order its operator== ignores -
|
// appends a new member at the end of such an object. Any other
|
||||||
// so a member-by-member diff always reproduces target there.
|
// object type places its members itself, so a member-by-member diff
|
||||||
|
// always reproduces target there.
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||||
#pragma warning(push )
|
#pragma warning(push )
|
||||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
||||||
#endif
|
#endif
|
||||||
if (!detail::is_ordered_map<object_t>::value
|
bool fast_path = true;
|
||||||
|| (common_keys_source_order == common_keys_target_order && new_keys_form_suffix))
|
if (detail::is_ordered_map<object_t>::value)
|
||||||
{
|
{
|
||||||
// fast path: order of common keys already matches (or the
|
// walk the common keys of source in lockstep with target: a key
|
||||||
// object_t's iteration order does not depend on
|
// of target that is not the next one is either new or out of order
|
||||||
// insertion history), so a plain per-key diff is correct
|
auto src = source.cbegin();
|
||||||
// and minimal, as before
|
std::size_t next = 0;
|
||||||
common_keys = std::move(common_keys_source_order);
|
bool seen_new = false;
|
||||||
return true;
|
for (auto it = target.cbegin(); it != target.cend() && fast_path; ++it)
|
||||||
|
{
|
||||||
|
while (next < target_values.size() && target_values[next] == nullptr)
|
||||||
|
{
|
||||||
|
++src;
|
||||||
|
++next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next < target_values.size() && src.key() == it.key())
|
||||||
|
{
|
||||||
|
// a common key after a new one: new keys are no suffix
|
||||||
|
fast_path = !seen_new;
|
||||||
|
++src;
|
||||||
|
++next;
|
||||||
|
}
|
||||||
|
else if (source.find(it.key()) == source.cend())
|
||||||
|
{
|
||||||
|
seen_new = true;
|
||||||
|
added.push_back(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// a common key out of order
|
||||||
|
fast_path = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (target.size() != common)
|
||||||
|
{
|
||||||
|
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||||
|
{
|
||||||
|
if (source.find(it.key()) == source.cend())
|
||||||
|
{
|
||||||
|
added.push_back(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||||
#pragma warning( pop )
|
#pragma warning( pop )
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (fast_path)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// slow path: the common keys are in a different relative
|
// slow path: the common keys are in a different relative
|
||||||
// order in source and target (only possible for a
|
// order in source and target (only possible for a
|
||||||
// reorderable object_t like ordered_map). Building a
|
// reorderable object_t like ordered_map). Building a
|
||||||
@@ -7352,6 +7441,14 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diff_unequal(result, source, target, path, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief @ref diff_recursively for values known to be unequal; callers
|
||||||
|
/// compare first so they build no path for equal ones
|
||||||
|
static void diff_unequal(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
|
const string_t& path, const std::size_t depth)
|
||||||
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
diff_iteratively(result, source, target, path);
|
diff_iteratively(result, source, target, path);
|
||||||
@@ -7374,7 +7471,10 @@ public:
|
|||||||
while (i < source.size() && i < target.size())
|
while (i < source.size() && i < target.size())
|
||||||
{
|
{
|
||||||
// recursive call to compare array values at index i
|
// recursive call to compare array values at index i
|
||||||
diff_recursively(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
|
if (!(source[i] == target[i]))
|
||||||
|
{
|
||||||
|
diff_unequal(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7386,25 +7486,24 @@ public:
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
std::vector<typename object_t::key_type> common_keys;
|
std::vector<const basic_json*> target_values;
|
||||||
basic_json added_ops(value_t::array);
|
std::vector<const_iterator> added;
|
||||||
if (diff_object_keys(result, source, target, path, common_keys, added_ops))
|
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||||
{
|
{
|
||||||
// fast path: common_keys is, by construction, the
|
// fast path: target_values holds, for every member of
|
||||||
// subsequence of source's keys that are common to both
|
// source in order, its value in target -- so no further
|
||||||
// objects, in source's iteration order -- so it can be
|
// lookup is needed. Deleted keys are interleaved here
|
||||||
// walked in lockstep with `source` using a cheap key
|
// too, in source's original order, to match the
|
||||||
// comparison instead of another lookup. Deleted keys
|
|
||||||
// (those source keys not in common_keys) are interleaved
|
|
||||||
// here too, in source's original order, to match the
|
|
||||||
// historical (pre-reordering-aware) output order.
|
// historical (pre-reordering-aware) output order.
|
||||||
auto common_it = common_keys.cbegin();
|
std::size_t i = 0;
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
|
||||||
{
|
{
|
||||||
if (common_it != common_keys.cend() && it.key() == *common_it)
|
if (target_values[i] != nullptr)
|
||||||
{
|
{
|
||||||
diff_recursively(result, it.value(), target[it.key()], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
|
if (!(it.value() == *target_values[i]))
|
||||||
++common_it;
|
{
|
||||||
|
diff_unequal(result, it.value(), *target_values[i], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -7413,10 +7512,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the "add" ops for brand-new keys collected by
|
// append the "add" operations for brand-new keys
|
||||||
// diff_object_keys -- no second source.find() per target
|
diff_added(result, added, path);
|
||||||
// key needed
|
|
||||||
result.insert(result.end(), added_ops.begin(), added_ops.end());
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -7489,17 +7586,17 @@ public:
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
std::vector<typename object_t::key_type> common_keys;
|
std::vector<const basic_json*> target_values;
|
||||||
basic_json added_ops(value_t::array);
|
std::vector<const_iterator> added;
|
||||||
if (diff_object_keys(result, s, t, current_path, common_keys, added_ops))
|
if (diff_object_keys(result, s, t, current_path, target_values, added))
|
||||||
{
|
{
|
||||||
// fast path: the frame walks source in lockstep with
|
// fast path: the frame walks source in lockstep with
|
||||||
// common_keys, as diff_recursively does, and appends
|
// common_keys, as diff_recursively does, and appends
|
||||||
// added_ops once all members are done
|
// added_ops once all members are done
|
||||||
stack.emplace_back(&s, &t, current_path.size());
|
stack.emplace_back(&s, &t, current_path.size());
|
||||||
stack.back().member = s.cbegin();
|
stack.back().member = s.cbegin();
|
||||||
stack.back().common_keys = std::move(common_keys);
|
stack.back().target_values = std::move(target_values);
|
||||||
stack.back().added_ops = std::move(added_ops);
|
stack.back().added = std::move(added);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -7561,11 +7658,10 @@ public:
|
|||||||
if (it != s->cend())
|
if (it != s->cend())
|
||||||
{
|
{
|
||||||
++stack.back().member;
|
++stack.back().member;
|
||||||
const std::size_t next_common = stack.back().next_common;
|
const basic_json* const target_value_ptr = stack.back().target_values[stack.back().index++];
|
||||||
if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common])
|
if (target_value_ptr != nullptr)
|
||||||
{
|
{
|
||||||
++stack.back().next_common;
|
const basic_json& target_value = *target_value_ptr;
|
||||||
const basic_json& target_value = (*t)[it.key()];
|
|
||||||
detail::concat_into(current_path, '/', detail::escape(it.key()));
|
detail::concat_into(current_path, '/', detail::escape(it.key()));
|
||||||
enter(it.value(), target_value);
|
enter(it.value(), target_value);
|
||||||
if (stack.size() == depth)
|
if (stack.size() == depth)
|
||||||
@@ -7583,7 +7679,7 @@ public:
|
|||||||
|
|
||||||
// append the "add" ops for brand-new keys collected when the
|
// append the "add" ops for brand-new keys collected when the
|
||||||
// object was entered
|
// object was entered
|
||||||
result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end());
|
diff_added(result, stack.back().added, current_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this array or object is done: continue with the one it is in
|
// this array or object is done: continue with the one it is in
|
||||||
|
|||||||
Generated
+307
-276
@@ -20080,40 +20080,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
{
|
return *m_it.array_iterator;
|
||||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
|
||||||
return m_it.object_iterator->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
|
||||||
return *m_it.array_iterator;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
throw_cannot_get_value();
|
|
||||||
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
|
||||||
{
|
|
||||||
return *m_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw_cannot_get_value();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||||
|
return m_it.object_iterator->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value (null has no value to point to)
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin()))
|
||||||
|
{
|
||||||
|
return *m_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_cannot_get_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -20124,38 +20109,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
{
|
return &*m_it.array_iterator;
|
||||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
|
||||||
return &(m_it.object_iterator->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
|
||||||
return &*m_it.array_iterator;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
|
||||||
{
|
|
||||||
return m_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw_cannot_get_value();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||||
|
return &(m_it.object_iterator->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
||||||
|
{
|
||||||
|
return m_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_cannot_get_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -20177,35 +20149,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
++m_it.array_iterator;
|
||||||
{
|
return *this;
|
||||||
std::advance(m_it.object_iterator, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
std::advance(m_it.array_iterator, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
++m_it.primitive_iterator;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
std::advance(m_it.object_iterator, 1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
++m_it.primitive_iterator;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20228,35 +20185,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
--m_it.array_iterator;
|
||||||
{
|
return *this;
|
||||||
std::advance(m_it.object_iterator, -1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
std::advance(m_it.array_iterator, -1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
--m_it.primitive_iterator;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
std::advance(m_it.object_iterator, -1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
--m_it.primitive_iterator;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20279,25 +20221,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
{
|
{
|
||||||
case value_t::object:
|
return (m_it.array_iterator == other.m_it.array_iterator);
|
||||||
return (m_it.object_iterator == other.m_it.object_iterator);
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return (m_it.array_iterator == other.m_it.array_iterator);
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
return (m_it.object_iterator == other.m_it.object_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither array nor object: a primitive value
|
||||||
|
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -30492,6 +30427,34 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// true if no child of the non-empty array/object v has children of
|
||||||
|
// its own, i.e. v can be freed in one go (its destructor then
|
||||||
|
// destroys scalars and empty containers only, so it recurses at most
|
||||||
|
// one level deep); stops at the first structured child it meets
|
||||||
|
static bool all_children_leaves(const basic_json& v) noexcept
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
const auto& a = *v.m_data.m_value.array;
|
||||||
|
for (auto it = a.rbegin(); it != a.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (!has_no_children(*it))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const auto& el : *v.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
if (!has_no_children(el.second))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// The walk in destroy_container() may take the children of a
|
// The walk in destroy_container() may take the children of a
|
||||||
// container in any order, as long as it picks the same child again
|
// container in any order, as long as it picks the same child again
|
||||||
// while that container is not modified in between. Arrays and
|
// while that container is not modified in between. Arrays and
|
||||||
@@ -30547,15 +30510,16 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deallocates the (already empty) array/object held by v; this is
|
// deallocates the array/object held by v, which is either empty or
|
||||||
// the same allocator-based free the old recursive implementation
|
// holds only scalars and empty containers (so its destructor
|
||||||
// used, just factored out so every level of the walk in destroy()
|
// recurses at most one level deep); this is the same allocator-based
|
||||||
// can share it
|
// free the old recursive implementation used, just factored out so
|
||||||
|
// every level of the walk in destroy() can share it
|
||||||
static void free_container(basic_json& v) noexcept
|
static void free_container(basic_json& v) noexcept
|
||||||
{
|
{
|
||||||
|
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
|
||||||
AllocatorType<array_t> alloc;
|
AllocatorType<array_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||||
@@ -30563,7 +30527,6 @@ private:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
|
||||||
AllocatorType<object_t> alloc;
|
AllocatorType<object_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||||
@@ -30608,40 +30571,99 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy the tree without recursing per nesting level and
|
// This json_value is not itself a basic_json, so the top-level
|
||||||
// without any heap allocation: a heap-allocated flattening
|
// container is first moved into a local stand-in ("cur"); a
|
||||||
// stack (the previous implementation) can itself throw
|
// default-constructed basic_json has a null pointer in its
|
||||||
// bad_alloc, which would escape this noexcept destructor and
|
// m_value (see data::m_value's initializer), so swapping it
|
||||||
// terminate the program (#5135).
|
// with *this leaves this union's own pointer null, and it is
|
||||||
//
|
// never looked at or freed a second time.
|
||||||
// Instead, walk down a chain of children (always the one
|
|
||||||
// walk_child() picks), reversing links as we go: cur is the
|
|
||||||
// container currently being emptied, and prev is its parent
|
|
||||||
// (value_t::null when there is none). Each parent's
|
|
||||||
// walk_child() slot doubles as storage for that parent's own
|
|
||||||
// parent link while we are below it, so no extra memory is
|
|
||||||
// needed; the parent is not modified meanwhile, so
|
|
||||||
// walk_child() finds that same slot again on the way up. We
|
|
||||||
// only ever remove a child once it is a scalar or an empty
|
|
||||||
// array/object, which neither allocates nor recurses more
|
|
||||||
// than one level deep.
|
|
||||||
//
|
|
||||||
// This json_value is not itself a basic_json, so the
|
|
||||||
// top-level container is first moved into a local stand-in
|
|
||||||
// ("cur"); a default-constructed basic_json has a null
|
|
||||||
// pointer in its m_value (see data::m_value's initializer),
|
|
||||||
// so swapping it with *this leaves this union's own pointer
|
|
||||||
// null, and it is never looked at or freed a second time.
|
|
||||||
basic_json cur;
|
basic_json cur;
|
||||||
cur.m_data.m_type = t;
|
cur.m_data.m_type = t;
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(cur.m_data.m_value, *this);
|
swap(cur.m_data.m_value, *this);
|
||||||
|
|
||||||
|
destroy_bounded(cur, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the array/object v, whose children are destroyed first.
|
||||||
|
// The first recursion_depth_limit() levels are destroyed by plain
|
||||||
|
// recursion, which is the fastest way and cannot throw; anything
|
||||||
|
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
||||||
|
static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
|
{
|
||||||
|
destroy_walk(v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
for (auto& child : *v.m_data.m_value.array)
|
||||||
|
{
|
||||||
|
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
||||||
|
{
|
||||||
|
destroy_bounded(child, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto& child : *v.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
||||||
|
{
|
||||||
|
destroy_bounded(child.second, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only leaves and already destroyed (null) children remain
|
||||||
|
free_container(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the tree below top without recursing per nesting level and
|
||||||
|
// without any heap allocation: a heap-allocated flattening
|
||||||
|
// stack (the previous implementation) can itself throw
|
||||||
|
// bad_alloc, which would escape this noexcept destructor and
|
||||||
|
// terminate the program (#5135).
|
||||||
|
//
|
||||||
|
// Instead, walk down a chain of children (always the one
|
||||||
|
// walk_child() picks), reversing links as we go: cur is the
|
||||||
|
// container currently being emptied, and prev is its parent
|
||||||
|
// (value_t::null when there is none). Each parent's
|
||||||
|
// walk_child() slot doubles as storage for that parent's own
|
||||||
|
// parent link while we are below it, so no extra memory is
|
||||||
|
// needed; the parent is not modified meanwhile, so
|
||||||
|
// walk_child() finds that same slot again on the way up. We
|
||||||
|
// only ever remove a child once it is a scalar or an empty
|
||||||
|
// array/object, which neither allocates nor recurses more
|
||||||
|
// than one level deep.
|
||||||
|
//
|
||||||
|
// This json_value is not itself a basic_json, so the
|
||||||
|
// top-level container is first moved into a local stand-in
|
||||||
|
// ("cur"); a default-constructed basic_json has a null
|
||||||
|
// pointer in its m_value (see data::m_value's initializer),
|
||||||
|
// so swapping it with *this leaves this union's own pointer
|
||||||
|
// null, and it is never looked at or freed a second time.
|
||||||
|
static void destroy_walk(basic_json& top) noexcept
|
||||||
|
{
|
||||||
|
basic_json cur;
|
||||||
|
take(cur, top);
|
||||||
|
|
||||||
basic_json prev; // value_t::null: no parent
|
basic_json prev; // value_t::null: no parent
|
||||||
|
|
||||||
|
// true right after cur was entered: its children have not been
|
||||||
|
// looked at yet. If none of them has children of its own, cur is
|
||||||
|
// freed in one go instead of child by child; this is tried once
|
||||||
|
// per container, so the walk stays linear.
|
||||||
|
bool fresh = true;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (has_no_children(cur))
|
const bool fresh_now = fresh;
|
||||||
|
fresh = false;
|
||||||
|
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
||||||
{
|
{
|
||||||
if (prev.m_data.m_type == value_t::null)
|
if (prev.m_data.m_type == value_t::null)
|
||||||
{
|
{
|
||||||
@@ -30680,6 +30702,7 @@ public:
|
|||||||
take(cur_child_ref, prev);
|
take(cur_child_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
take(cur, tmp);
|
take(cur, tmp);
|
||||||
|
fresh = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36895,12 +36918,11 @@ public:
|
|||||||
std::size_t index = 0;
|
std::size_t index = 0;
|
||||||
/// objects: the next member of source to look at
|
/// objects: the next member of source to look at
|
||||||
const_iterator member{}; // NOLINT(readability-redundant-member-init)
|
const_iterator member{}; // NOLINT(readability-redundant-member-init)
|
||||||
/// objects: the keys common to both, in source's order
|
/// objects: for each member of source (in order), its value in
|
||||||
std::vector<typename object_t::key_type> common_keys{}; // NOLINT(readability-redundant-member-init)
|
/// target, or nullptr if target has no such key; `index` is the next
|
||||||
/// objects: the next entry of common_keys
|
std::vector<const basic_json*> target_values{}; // NOLINT(readability-redundant-member-init)
|
||||||
std::size_t next_common = 0;
|
/// objects: the members only target has, in target's order
|
||||||
/// objects: the "add" operations for keys only target has
|
std::vector<const_iterator> added{}; // NOLINT(readability-redundant-member-init)
|
||||||
basic_json added_ops{}; // NOLINT(readability-redundant-member-init)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The operations of a diff are built by the functions below rather than
|
// The operations of a diff are built by the functions below rather than
|
||||||
@@ -36956,111 +36978,113 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief appends the "add" operations for the members @a added to @a result
|
||||||
|
static void diff_added(basic_json& result, const std::vector<const_iterator>& added, const string_t& path)
|
||||||
|
{
|
||||||
|
for (const auto& it : added)
|
||||||
|
{
|
||||||
|
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief compare the keys of objects @a source and @a target
|
@brief compare the keys of objects @a source and @a target
|
||||||
|
|
||||||
If object_t does not keep its members in insertion order, or if the keys
|
If object_t does not keep its members in insertion order, or if the keys
|
||||||
both objects have are in the same order in both, and the keys only
|
both objects have are in the same order in both, and the keys only
|
||||||
@a target has come after them, stores the keys common to both in
|
@a target has come after them, stores in @a target_values, for each member
|
||||||
source's order in @a common_keys, stores the "add" operations for the keys
|
of source in order, its value in target (nullptr for a key target does not
|
||||||
only @a target has in @a added_ops, and returns true: the caller then diffs
|
have), stores the members only target has in @a added, and returns true:
|
||||||
the objects member by member. Otherwise, appends operations that remove
|
the caller then diffs the objects member by member. Otherwise, appends
|
||||||
every member of @a source and add every member of @a target to @a result,
|
operations that remove every member of @a source and add every member of
|
||||||
and returns false.
|
@a target to @a result, and returns false.
|
||||||
*/
|
*/
|
||||||
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
const string_t& path, std::vector<typename object_t::key_type>& common_keys,
|
const string_t& path, std::vector<const basic_json*>& target_values,
|
||||||
basic_json& added_ops)
|
std::vector<const_iterator>& added)
|
||||||
{
|
{
|
||||||
// first pass: record, for every source key, whether it is
|
// first pass: one lookup per source key
|
||||||
// common to both objects (in source's iteration order) or
|
target_values.reserve(source.size());
|
||||||
// was deleted (i.e., in source but not in target) -- this is
|
std::size_t common = 0;
|
||||||
// a by-product of the target.find() call already needed to
|
|
||||||
// tell the two cases apart, so it adds no extra lookups. The
|
|
||||||
// "remove" ops themselves are emitted later, interleaved
|
|
||||||
// with the per-key diffs in the caller's fast path, to match
|
|
||||||
// source's original iteration order (as the original,
|
|
||||||
// pre-reordering-aware implementation did) instead of
|
|
||||||
// grouping all removes before all per-key diffs.
|
|
||||||
std::vector<typename object_t::key_type> common_keys_source_order;
|
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
{
|
{
|
||||||
if (target.find(it.key()) != target.end())
|
const auto found = target.find(it.key());
|
||||||
|
if (found != target.cend())
|
||||||
{
|
{
|
||||||
common_keys_source_order.push_back(it.key());
|
target_values.push_back(&found.value());
|
||||||
}
|
++common;
|
||||||
}
|
|
||||||
|
|
||||||
// second pass: find keys that were added (i.e., in target but
|
|
||||||
// not in source), and record the keys common to both, in
|
|
||||||
// target's iteration order -- again a by-product of the
|
|
||||||
// source.find() call already needed to detect added keys. At
|
|
||||||
// the same time, determine whether every added key comes
|
|
||||||
// after every common key in target's order (a precondition
|
|
||||||
// for the fast path, which only ever appends new keys
|
|
||||||
// at the very end). Both are only needed for an object_t that
|
|
||||||
// keeps its members in insertion order, such as the one
|
|
||||||
// backing `ordered_json`; for any other object_t, the fast
|
|
||||||
// path is always taken and they are not computed.
|
|
||||||
// The patch ops for keys that were added (i.e., in target but not
|
|
||||||
// in source) are built here so the fast path can reuse
|
|
||||||
// them without a second source.find() per target key. Only
|
|
||||||
// used by the fast path -- the slow (reordering) path
|
|
||||||
// rebuilds "add" ops for every key itself.
|
|
||||||
std::vector<typename object_t::key_type> common_keys_target_order;
|
|
||||||
bool new_keys_form_suffix = true;
|
|
||||||
bool seen_new_key = false;
|
|
||||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
|
||||||
{
|
|
||||||
if (source.find(it.key()) == source.end())
|
|
||||||
{
|
|
||||||
seen_new_key = true;
|
|
||||||
diff_add(added_ops, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
target_values.push_back(nullptr);
|
||||||
#pragma warning(push )
|
|
||||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
|
||||||
#endif
|
|
||||||
if (detail::is_ordered_map<object_t>::value)
|
|
||||||
{
|
|
||||||
common_keys_target_order.push_back(it.key());
|
|
||||||
if (seen_new_key)
|
|
||||||
{
|
|
||||||
new_keys_form_suffix = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
|
||||||
#pragma warning( pop )
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only an object type that keeps its members in insertion
|
// second pass: find the keys only target has. An object type that
|
||||||
// order, such as nlohmann::ordered_map, can need reordering:
|
// keeps its members in insertion order, such as the one backing
|
||||||
// patch() appends a new member at the end of such an object.
|
// ordered_json, also needs the common keys to be in the same order
|
||||||
// Any other object type places its members itself - std::map
|
// in both objects, and the new keys to come after them, as patch()
|
||||||
// in key order, a hash map in an order its operator== ignores -
|
// appends a new member at the end of such an object. Any other
|
||||||
// so a member-by-member diff always reproduces target there.
|
// object type places its members itself, so a member-by-member diff
|
||||||
|
// always reproduces target there.
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||||
#pragma warning(push )
|
#pragma warning(push )
|
||||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
||||||
#endif
|
#endif
|
||||||
if (!detail::is_ordered_map<object_t>::value
|
bool fast_path = true;
|
||||||
|| (common_keys_source_order == common_keys_target_order && new_keys_form_suffix))
|
if (detail::is_ordered_map<object_t>::value)
|
||||||
{
|
{
|
||||||
// fast path: order of common keys already matches (or the
|
// walk the common keys of source in lockstep with target: a key
|
||||||
// object_t's iteration order does not depend on
|
// of target that is not the next one is either new or out of order
|
||||||
// insertion history), so a plain per-key diff is correct
|
auto src = source.cbegin();
|
||||||
// and minimal, as before
|
std::size_t next = 0;
|
||||||
common_keys = std::move(common_keys_source_order);
|
bool seen_new = false;
|
||||||
return true;
|
for (auto it = target.cbegin(); it != target.cend() && fast_path; ++it)
|
||||||
|
{
|
||||||
|
while (next < target_values.size() && target_values[next] == nullptr)
|
||||||
|
{
|
||||||
|
++src;
|
||||||
|
++next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next < target_values.size() && src.key() == it.key())
|
||||||
|
{
|
||||||
|
// a common key after a new one: new keys are no suffix
|
||||||
|
fast_path = !seen_new;
|
||||||
|
++src;
|
||||||
|
++next;
|
||||||
|
}
|
||||||
|
else if (source.find(it.key()) == source.cend())
|
||||||
|
{
|
||||||
|
seen_new = true;
|
||||||
|
added.push_back(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// a common key out of order
|
||||||
|
fast_path = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (target.size() != common)
|
||||||
|
{
|
||||||
|
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||||
|
{
|
||||||
|
if (source.find(it.key()) == source.cend())
|
||||||
|
{
|
||||||
|
added.push_back(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||||
#pragma warning( pop )
|
#pragma warning( pop )
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (fast_path)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// slow path: the common keys are in a different relative
|
// slow path: the common keys are in a different relative
|
||||||
// order in source and target (only possible for a
|
// order in source and target (only possible for a
|
||||||
// reorderable object_t like ordered_map). Building a
|
// reorderable object_t like ordered_map). Building a
|
||||||
@@ -37110,6 +37134,14 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diff_unequal(result, source, target, path, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief @ref diff_recursively for values known to be unequal; callers
|
||||||
|
/// compare first so they build no path for equal ones
|
||||||
|
static void diff_unequal(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
|
const string_t& path, const std::size_t depth)
|
||||||
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
diff_iteratively(result, source, target, path);
|
diff_iteratively(result, source, target, path);
|
||||||
@@ -37132,7 +37164,10 @@ public:
|
|||||||
while (i < source.size() && i < target.size())
|
while (i < source.size() && i < target.size())
|
||||||
{
|
{
|
||||||
// recursive call to compare array values at index i
|
// recursive call to compare array values at index i
|
||||||
diff_recursively(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
|
if (!(source[i] == target[i]))
|
||||||
|
{
|
||||||
|
diff_unequal(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37144,25 +37179,24 @@ public:
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
std::vector<typename object_t::key_type> common_keys;
|
std::vector<const basic_json*> target_values;
|
||||||
basic_json added_ops(value_t::array);
|
std::vector<const_iterator> added;
|
||||||
if (diff_object_keys(result, source, target, path, common_keys, added_ops))
|
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||||
{
|
{
|
||||||
// fast path: common_keys is, by construction, the
|
// fast path: target_values holds, for every member of
|
||||||
// subsequence of source's keys that are common to both
|
// source in order, its value in target -- so no further
|
||||||
// objects, in source's iteration order -- so it can be
|
// lookup is needed. Deleted keys are interleaved here
|
||||||
// walked in lockstep with `source` using a cheap key
|
// too, in source's original order, to match the
|
||||||
// comparison instead of another lookup. Deleted keys
|
|
||||||
// (those source keys not in common_keys) are interleaved
|
|
||||||
// here too, in source's original order, to match the
|
|
||||||
// historical (pre-reordering-aware) output order.
|
// historical (pre-reordering-aware) output order.
|
||||||
auto common_it = common_keys.cbegin();
|
std::size_t i = 0;
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i)
|
||||||
{
|
{
|
||||||
if (common_it != common_keys.cend() && it.key() == *common_it)
|
if (target_values[i] != nullptr)
|
||||||
{
|
{
|
||||||
diff_recursively(result, it.value(), target[it.key()], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
|
if (!(it.value() == *target_values[i]))
|
||||||
++common_it;
|
{
|
||||||
|
diff_unequal(result, it.value(), *target_values[i], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -37171,10 +37205,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the "add" ops for brand-new keys collected by
|
// append the "add" operations for brand-new keys
|
||||||
// diff_object_keys -- no second source.find() per target
|
diff_added(result, added, path);
|
||||||
// key needed
|
|
||||||
result.insert(result.end(), added_ops.begin(), added_ops.end());
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -37247,17 +37279,17 @@ public:
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
std::vector<typename object_t::key_type> common_keys;
|
std::vector<const basic_json*> target_values;
|
||||||
basic_json added_ops(value_t::array);
|
std::vector<const_iterator> added;
|
||||||
if (diff_object_keys(result, s, t, current_path, common_keys, added_ops))
|
if (diff_object_keys(result, s, t, current_path, target_values, added))
|
||||||
{
|
{
|
||||||
// fast path: the frame walks source in lockstep with
|
// fast path: the frame walks source in lockstep with
|
||||||
// common_keys, as diff_recursively does, and appends
|
// common_keys, as diff_recursively does, and appends
|
||||||
// added_ops once all members are done
|
// added_ops once all members are done
|
||||||
stack.emplace_back(&s, &t, current_path.size());
|
stack.emplace_back(&s, &t, current_path.size());
|
||||||
stack.back().member = s.cbegin();
|
stack.back().member = s.cbegin();
|
||||||
stack.back().common_keys = std::move(common_keys);
|
stack.back().target_values = std::move(target_values);
|
||||||
stack.back().added_ops = std::move(added_ops);
|
stack.back().added = std::move(added);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -37319,11 +37351,10 @@ public:
|
|||||||
if (it != s->cend())
|
if (it != s->cend())
|
||||||
{
|
{
|
||||||
++stack.back().member;
|
++stack.back().member;
|
||||||
const std::size_t next_common = stack.back().next_common;
|
const basic_json* const target_value_ptr = stack.back().target_values[stack.back().index++];
|
||||||
if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common])
|
if (target_value_ptr != nullptr)
|
||||||
{
|
{
|
||||||
++stack.back().next_common;
|
const basic_json& target_value = *target_value_ptr;
|
||||||
const basic_json& target_value = (*t)[it.key()];
|
|
||||||
detail::concat_into(current_path, '/', detail::escape(it.key()));
|
detail::concat_into(current_path, '/', detail::escape(it.key()));
|
||||||
enter(it.value(), target_value);
|
enter(it.value(), target_value);
|
||||||
if (stack.size() == depth)
|
if (stack.size() == depth)
|
||||||
@@ -37341,7 +37372,7 @@ public:
|
|||||||
|
|
||||||
// append the "add" ops for brand-new keys collected when the
|
// append the "add" ops for brand-new keys collected when the
|
||||||
// object was entered
|
// object was entered
|
||||||
result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end());
|
diff_added(result, stack.back().added, current_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this array or object is done: continue with the one it is in
|
// this array or object is done: continue with the one it is in
|
||||||
|
|||||||
@@ -323,3 +323,57 @@ TEST_CASE("copy constructor and destructor across value types")
|
|||||||
CHECK(w.size() == 9);
|
CHECK(w.size() == 9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("destructor of nested containers", Json, nlohmann::json, nlohmann::ordered_json)
|
||||||
|
{
|
||||||
|
SECTION("empty containers")
|
||||||
|
{
|
||||||
|
{
|
||||||
|
Json j = Json::array();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Json j = Json::object();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Json j = Json::array({Json::array(), Json::object(), Json::array({Json::array()})});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("leaves mixed with nested containers")
|
||||||
|
{
|
||||||
|
Json j = Json::object();
|
||||||
|
j["leaf"] = 1;
|
||||||
|
j["str"] = "text";
|
||||||
|
j["empty_a"] = Json::array();
|
||||||
|
j["empty_o"] = Json::object();
|
||||||
|
j["arr"] = Json::array({1, "a", Json::array({1, 2}), Json::object({{"k", Json::array({Json::object()})}}), nullptr});
|
||||||
|
j["obj"] = Json::object({{"x", Json::object({{"y", Json::array({1, Json::object({{"z", 1}})})}})}, {"w", 2}});
|
||||||
|
Json copy = j;
|
||||||
|
CHECK(copy == j);
|
||||||
|
j = Json::array({std::move(copy), j, 3, Json::array()});
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting")
|
||||||
|
{
|
||||||
|
for (int kind = 0; kind < 3; ++kind)
|
||||||
|
{
|
||||||
|
Json j = 1;
|
||||||
|
for (int i = 0; i < 5000; ++i)
|
||||||
|
{
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
j = Json::array({std::move(j)});
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
j = Json::object({{"k", std::move(j)}});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// siblings next to the nested child
|
||||||
|
j = (i % 2 == 0) ? Json::array({1, Json::array(), std::move(j)}) : Json::object({{"a", 1}, {"b", Json::object()}, {"c", std::move(j)}});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#define JSON_TESTS_PRIVATE
|
#define JSON_TESTS_PRIVATE
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
TEST_CASE("iterators 1")
|
TEST_CASE("iterators 1")
|
||||||
@@ -1628,3 +1630,108 @@ TEST_CASE("iterators 1")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json, nlohmann::ordered_json)
|
||||||
|
{
|
||||||
|
SECTION("array: -- and rbegin/rend")
|
||||||
|
{
|
||||||
|
Json j = {1, 2, 3, 4};
|
||||||
|
|
||||||
|
auto it = j.end();
|
||||||
|
std::vector<int> seen;
|
||||||
|
while (it != j.begin())
|
||||||
|
{
|
||||||
|
--it;
|
||||||
|
seen.push_back(it->template get<int>());
|
||||||
|
}
|
||||||
|
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||||
|
|
||||||
|
auto it2 = j.end();
|
||||||
|
auto old = it2--;
|
||||||
|
CHECK(old == j.end());
|
||||||
|
CHECK(*it2 == 4);
|
||||||
|
|
||||||
|
seen.clear();
|
||||||
|
for (auto r = j.rbegin(); r != j.rend(); ++r)
|
||||||
|
{
|
||||||
|
seen.push_back(r->template get<int>());
|
||||||
|
}
|
||||||
|
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||||
|
|
||||||
|
seen.clear();
|
||||||
|
for (auto r = j.crbegin(); r != j.crend(); ++r)
|
||||||
|
{
|
||||||
|
seen.push_back(*r);
|
||||||
|
}
|
||||||
|
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object: -- and rbegin/rend")
|
||||||
|
{
|
||||||
|
Json j = {{"a", 1}, {"b", 2}, {"c", 3}};
|
||||||
|
// order of keys depends on the object type; use the forward order as reference
|
||||||
|
std::vector<std::string> fwd;
|
||||||
|
for (auto it = j.begin(); it != j.end(); ++it)
|
||||||
|
{
|
||||||
|
fwd.push_back(it.key());
|
||||||
|
}
|
||||||
|
std::vector<std::string> rev(fwd.rbegin(), fwd.rend());
|
||||||
|
|
||||||
|
std::vector<std::string> seen;
|
||||||
|
auto it = j.end();
|
||||||
|
while (it != j.begin())
|
||||||
|
{
|
||||||
|
--it;
|
||||||
|
seen.push_back(it.key());
|
||||||
|
}
|
||||||
|
CHECK(seen == rev);
|
||||||
|
|
||||||
|
auto it2 = j.end();
|
||||||
|
auto old = it2--;
|
||||||
|
CHECK(old == j.end());
|
||||||
|
CHECK(it2.key() == rev.front());
|
||||||
|
|
||||||
|
seen.clear();
|
||||||
|
for (auto r = j.rbegin(); r != j.rend(); ++r)
|
||||||
|
{
|
||||||
|
seen.push_back(r.key());
|
||||||
|
}
|
||||||
|
CHECK(seen == rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object: operator-> reads and writes the mapped value")
|
||||||
|
{
|
||||||
|
Json j = {{"a", 1}, {"s", "str"}};
|
||||||
|
auto it = j.find("s");
|
||||||
|
REQUIRE(it != j.end());
|
||||||
|
CHECK(it->is_string());
|
||||||
|
CHECK(it->size() == 1);
|
||||||
|
*it = Json::array({1, 2});
|
||||||
|
CHECK(it->is_array());
|
||||||
|
it->push_back(3);
|
||||||
|
CHECK(j["s"].size() == 3);
|
||||||
|
|
||||||
|
const Json& cj = j;
|
||||||
|
auto cit = cj.find("a");
|
||||||
|
REQUIRE(cit != cj.end());
|
||||||
|
CHECK(cit->is_number_integer());
|
||||||
|
CHECK(cit->template get<int>() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("primitives: -- and ->")
|
||||||
|
{
|
||||||
|
Json j = 42;
|
||||||
|
auto it = j.end();
|
||||||
|
--it;
|
||||||
|
CHECK(it == j.begin());
|
||||||
|
CHECK(it->template get<int>() == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comparing iterators of different containers throws")
|
||||||
|
{
|
||||||
|
Json a = {1, 2};
|
||||||
|
Json b = {1, 2};
|
||||||
|
CHECK_THROWS_WITH_AS(a.begin() == b.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", nlohmann::json::invalid_iterator);
|
||||||
|
CHECK_THROWS_WITH_AS(a.begin() < b.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", nlohmann::json::invalid_iterator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2066,3 +2066,40 @@ TEST_CASE("JSON patch - every operation on ordered_json")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("JSON patch - diff round trip on hand-made documents", Json, nlohmann::json, nlohmann::ordered_json)
|
||||||
|
{
|
||||||
|
const auto roundtrip = [](const Json & a, const Json & b)
|
||||||
|
{
|
||||||
|
CAPTURE(a.dump())
|
||||||
|
CAPTURE(b.dump())
|
||||||
|
const Json p = Json::diff(a, b);
|
||||||
|
CHECK(a.patch(p) == b);
|
||||||
|
CHECK(Json::diff(b, b).empty());
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("reordered keys")
|
||||||
|
{
|
||||||
|
const Json a = Json::parse(R"({"x":1,"y":2,"z":{"p":1,"q":2}})");
|
||||||
|
const Json b = Json::parse(R"({"z":{"q":2,"p":1},"y":2,"x":1})");
|
||||||
|
roundtrip(a, b);
|
||||||
|
roundtrip(b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested add, remove and replace")
|
||||||
|
{
|
||||||
|
const Json a = Json::parse(R"({"a":[1,2,{"k":"v","gone":true}],"b":{"c":{"d":1}},"e":"s"})");
|
||||||
|
const Json b = Json::parse(R"({"a":[1,3,{"k":"w","new":[]}],"b":{"c":{"d":[1,2]}},"f":null})");
|
||||||
|
roundtrip(a, b);
|
||||||
|
roundtrip(b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("arrays of different length and type changes")
|
||||||
|
{
|
||||||
|
roundtrip(Json::parse("[1,2,3,4,5]"), Json::parse("[1,3]"));
|
||||||
|
roundtrip(Json::parse("[1]"), Json::parse("[1,[2],{\"a\":3}]"));
|
||||||
|
roundtrip(Json::parse("[]"), Json::parse("{}"));
|
||||||
|
roundtrip(Json::parse("{\"a\":[]}"), Json::parse("{\"a\":{}}"));
|
||||||
|
roundtrip(Json(1), Json::parse("[1]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user