mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 02:37:13 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8e4e38284 |
No files matched your search
@@ -280,6 +280,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @throw invalid_iterator.214 always
|
/// @throw invalid_iterator.214 always
|
||||||
|
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN void throw_compare_different_values() const
|
||||||
|
{
|
||||||
|
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
|
||||||
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const
|
JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const
|
||||||
{
|
{
|
||||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object));
|
JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object));
|
||||||
@@ -294,6 +299,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
|
return *m_it.array_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
@@ -338,6 +355,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
|
return &*m_it.array_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
@@ -391,6 +414,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
++m_it.array_iterator;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
std::advance(m_it.object_iterator, 1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
@@ -484,7 +519,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
// if objects are not the same, the comparison is undefined
|
// if objects are not the same, the comparison is undefined
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||||
{
|
{
|
||||||
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
|
throw_compare_different_values();
|
||||||
}
|
}
|
||||||
|
|
||||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||||
@@ -493,6 +528,16 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
return (m_it.array_iterator == other.m_it.array_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
return (m_it.object_iterator == other.m_it.object_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
|
|||||||
+162
-118
@@ -720,6 +720,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
|
||||||
@@ -775,7 +803,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deallocates the (already empty) array/object held by v; this is
|
// deallocates the array/object held by v (empty, or holding only scalars
|
||||||
|
// and empty containers); this is
|
||||||
// the same allocator-based free the old recursive implementation
|
// the same allocator-based free the old recursive implementation
|
||||||
// used, just factored out so every level of the walk in destroy()
|
// used, just factored out so every level of the walk in destroy()
|
||||||
// can share it
|
// can share it
|
||||||
@@ -783,7 +812,6 @@ private:
|
|||||||
{
|
{
|
||||||
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);
|
||||||
@@ -791,7 +819,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);
|
||||||
@@ -867,9 +894,17 @@ public:
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -908,6 +943,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6878,12 +6914,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
|
||||||
@@ -6939,111 +6974,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
|
||||||
@@ -7093,6 +7130,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);
|
||||||
@@ -7115,7 +7160,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7127,25 +7175,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
|
||||||
{
|
{
|
||||||
@@ -7154,10 +7201,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;
|
||||||
}
|
}
|
||||||
@@ -7230,17 +7275,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;
|
||||||
}
|
}
|
||||||
@@ -7302,11 +7347,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)
|
||||||
@@ -7324,7 +7368,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
+208
-119
@@ -19929,6 +19929,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @throw invalid_iterator.214 always
|
/// @throw invalid_iterator.214 always
|
||||||
|
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN void throw_compare_different_values() const
|
||||||
|
{
|
||||||
|
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
|
||||||
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const
|
JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const
|
||||||
{
|
{
|
||||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object));
|
JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object));
|
||||||
@@ -19943,6 +19948,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
|
return *m_it.array_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
@@ -19987,6 +20004,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||||
|
return &*m_it.array_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
@@ -20040,6 +20063,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
++m_it.array_iterator;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
std::advance(m_it.object_iterator, 1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
@@ -20133,7 +20168,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
// if objects are not the same, the comparison is undefined
|
// if objects are not the same, the comparison is undefined
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||||
{
|
{
|
||||||
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
|
throw_compare_different_values();
|
||||||
}
|
}
|
||||||
|
|
||||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||||
@@ -20142,6 +20177,16 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||||
|
{
|
||||||
|
return (m_it.array_iterator == other.m_it.array_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||||
|
{
|
||||||
|
return (m_it.object_iterator == other.m_it.object_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
switch (m_object->m_data.m_type)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
@@ -30068,6 +30113,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
|
||||||
@@ -30123,7 +30196,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deallocates the (already empty) array/object held by v; this is
|
// deallocates the array/object held by v (empty, or holding only scalars
|
||||||
|
// and empty containers); this is
|
||||||
// the same allocator-based free the old recursive implementation
|
// the same allocator-based free the old recursive implementation
|
||||||
// used, just factored out so every level of the walk in destroy()
|
// used, just factored out so every level of the walk in destroy()
|
||||||
// can share it
|
// can share it
|
||||||
@@ -30131,7 +30205,6 @@ private:
|
|||||||
{
|
{
|
||||||
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);
|
||||||
@@ -30139,7 +30212,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);
|
||||||
@@ -30215,9 +30287,17 @@ public:
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -30256,6 +30336,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36226,12 +36307,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
|
||||||
@@ -36287,111 +36367,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
|
||||||
@@ -36441,6 +36523,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);
|
||||||
@@ -36463,7 +36553,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36475,25 +36568,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
|
||||||
{
|
{
|
||||||
@@ -36502,10 +36594,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;
|
||||||
}
|
}
|
||||||
@@ -36578,17 +36668,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;
|
||||||
}
|
}
|
||||||
@@ -36650,11 +36740,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)
|
||||||
@@ -36672,7 +36761,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
|
||||||
|
|||||||
Reference in new issue
Block a user