Compare commits

..
Author SHA1 Message Date
Niels Lohmann dbf9f45455 Update cppcheck to 1.5.2 (Cppcheck 2.20) and fix its new findings
Cppcheck 2.20 rejects invocations of unknown function-like macros in #if
conditions instead of evaluating them as 0. default_defines.hpp expands
the Hedley macros to the compiler's feature-test operators (__has_attribute
etc.), so define them as 0 in a generated include file.

It also reports a false bufferAccessOutOfBounds in the float overload of
float_from_bits, sizing the parameter as the double overload's; use the
size of the source instead (equal by the static_assert).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:58:07 +02:00
14 changed files with 264 additions and 436 deletions

No files matched your search

+8 -1
View File
@@ -485,6 +485,13 @@ add_custom_target(ci_clang_analyze
# Check code with Cppcheck. # Check code with Cppcheck.
############################################################################### ###############################################################################
# default_defines.hpp expands the Hedley macros to the compiler's feature-test operators, which Cppcheck does not know.
# Since Cppcheck 2.20, it rejects their invocations in #if conditions instead of evaluating them as 0.
file(WRITE ${CMAKE_BINARY_DIR}/cppcheck_feature_tests.hpp "")
foreach(OPERATOR __has_attribute __has_builtin __has_cpp_attribute __has_declspec_attribute __has_extension __has_feature __has_include __has_warning)
file(APPEND ${CMAKE_BINARY_DIR}/cppcheck_feature_tests.hpp "#define ${OPERATOR}(x) 0\n")
endforeach()
add_custom_target(ci_cppcheck add_custom_target(ci_cppcheck
COMMAND mkdir -p cppcheck COMMAND mkdir -p cppcheck
COMMAND clang -dM -E -x c++ -std=c++11 ${CMAKE_SOURCE_DIR}/include/nlohmann/thirdparty/hedley/hedley.hpp > default_defines.hpp 2> /dev/null COMMAND clang -dM -E -x c++ -std=c++11 ${CMAKE_SOURCE_DIR}/include/nlohmann/thirdparty/hedley/hedley.hpp > default_defines.hpp 2> /dev/null
@@ -493,7 +500,7 @@ add_custom_target(ci_cppcheck
COMMAND venv_cppcheck/bin/cppcheck --enable=warning --check-level=exhaustive --inline-suppr --inconclusive --force COMMAND venv_cppcheck/bin/cppcheck --enable=warning --check-level=exhaustive --inline-suppr --inconclusive --force
--std=c++11 ${PROJECT_SOURCE_DIR}/include/nlohmann/json.hpp -I ${CMAKE_SOURCE_DIR}/include --std=c++11 ${PROJECT_SOURCE_DIR}/include/nlohmann/json.hpp -I ${CMAKE_SOURCE_DIR}/include
--error-exitcode=1 --relative-paths=${PROJECT_SOURCE_DIR} -j ${N} --include=default_defines.hpp --error-exitcode=1 --relative-paths=${PROJECT_SOURCE_DIR} -j ${N} --include=default_defines.hpp
--cppcheck-build-dir=cppcheck --include=${CMAKE_BINARY_DIR}/cppcheck_feature_tests.hpp --cppcheck-build-dir=cppcheck
-UJSON_CATCH_USER -UJSON_TRY_USER -UJSON_ASSERT -UJSON_INTERNAL_CATCH -UJSON_THROW -UJSON_CATCH_USER -UJSON_TRY_USER -UJSON_ASSERT -UJSON_INTERNAL_CATCH -UJSON_THROW
-DJSON_HAS_CPP_11 -UJSON_HAS_CPP_14 -UJSON_HAS_CPP_17 -UJSON_HAS_CPP_20 -UJSON_HAS_THREE_WAY_COMPARISON -DJSON_HAS_CPP_11 -UJSON_HAS_CPP_14 -UJSON_HAS_CPP_17 -UJSON_HAS_CPP_20 -UJSON_HAS_THREE_WAY_COMPARISON
COMMENT "Check code with Cppcheck" COMMENT "Check code with Cppcheck"
+1 -1
View File
@@ -1 +1 @@
cppcheck==1.5.1 cppcheck==1.5.2
+1 -1
View File
@@ -27,7 +27,7 @@ To store objects in C++, a type is defined by the template parameters explained
### Default type ### Default type
With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default type for With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default value for
`array_t` is: `array_t` is:
```cpp ```cpp
+1 -1
View File
@@ -23,7 +23,7 @@ To store boolean values in C++, a type is defined by the template parameter `Bo
### Default type ### Default type
With the default values for `BooleanType` (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`. With the default values for `BooleanType` (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`.
### Storage ### Storage
@@ -35,7 +35,7 @@ type to use.
### Default type ### Default type
With the default values for `NumberFloatType` (`double`), the default type for `number_float_t` is `#!cpp double`. With the default values for `NumberFloatType` (`double`), the default value for `number_float_t` is `#!cpp double`.
### Default behavior ### Default behavior
@@ -52,9 +52,8 @@ With the default values for `NumberFloatType` (`double`), the default type for `
> interoperability can be achieved by implementations that expect no more precision or range than these provide, in the > interoperability can be achieved by implementations that expect no more precision or range than these provide, in the
> sense that implementations will approximate JSON numbers within the expected precision. > sense that implementations will approximate JSON numbers within the expected precision.
This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default. This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that
Note that with the default `double`, numbers smaller than `-1.79769313486232e+308` or greater than numbers smaller than `-1.79769313486232e+308` or greater than `1.79769313486232e+308` are rejected during parsing with
`1.79769313486232e+308` are rejected during parsing with
[`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can
only be stored by assigning them directly and are serialized to `null`. only be stored by assigning them directly and are serialized to `null`.
@@ -31,7 +31,7 @@ to use.
### Default type ### Default type
With the default values for `NumberIntegerType` (`std::int64_t`), the default type for `number_integer_t` is With the default values for `NumberIntegerType` (`std::int64_t`), the default value for `number_integer_t` is
`#!cpp std::int64_t`. `#!cpp std::int64_t`.
### Default behavior ### Default behavior
@@ -32,7 +32,7 @@ the type to use.
### Default type ### Default type
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default type for `number_unsigned_t` is With the default values for `NumberUnsignedType` (`std::uint64_t`), the default value for `number_unsigned_t` is
`#!cpp std::uint64_t`. `#!cpp std::uint64_t`.
### Default behavior ### Default behavior
+1 -1
View File
@@ -36,7 +36,7 @@ To store objects in C++, a type is defined by the template parameters described
### Default type ### Default type
With the default values for `ObjectType` (`std::map`), `StringType` (`std::string`), and `AllocatorType` With the default values for `ObjectType` (`std::map`), `StringType` (`std::string`), and `AllocatorType`
(`std::allocator`), the default type for `object_t` is: (`std::allocator`), the default value for `object_t` is:
```cpp ```cpp
// until C++14 // until C++14
+1 -1
View File
@@ -32,7 +32,7 @@ JSON class into byte-sized characters during deserialization.
### Default type ### Default type
With the default values for `StringType` (`std::string`), the default type for `string_t` is `#!cpp std::string`. With the default values for `StringType` (`std::string`), the default value for `string_t` is `#!cpp std::string`.
### Encoding ### Encoding
+8 -8
View File
@@ -120,7 +120,7 @@ requirements are collected in [Template Parameter Requirements](template_paramet
### Default type ### Default type
With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default type for `object_t` is: With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default value for `object_t` is:
```cpp ```cpp
std::map< std::map<
@@ -177,7 +177,7 @@ json j = m;
### Default type ### Default type
With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default type for `array_t` is: With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default value for `array_t` is:
```cpp ```cpp
std::vector< std::vector<
@@ -209,7 +209,7 @@ Unicode values are split by the JSON class into byte-sized characters during des
### Default type ### Default type
With the default values for *StringType* (`std::string`), the default type for `string_t` is `#!cpp std::string`. With the default values for *StringType* (`std::string`), the default value for `string_t` is `#!cpp std::string`.
### Encoding ### Encoding
@@ -234,7 +234,7 @@ String values are stored as pointers in a `basic_json` type. That is, for any ac
### Default type ### Default type
With the default values for *BooleanType* (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`. With the default values for *BooleanType* (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`.
### Storage ### Storage
@@ -252,9 +252,9 @@ This description includes both integer and floating-point numbers. However, C++
### Default types ### Default types
With the default values for *NumberIntegerType* (`std::int64_t`), the default type for `number_integer_t` is `std::int64_t`. With the default values for *NumberIntegerType* (`std::int64_t`), the default value for `number_integer_t` is `std::int64_t`.
With the default values for *NumberUnsignedType* (`std::uint64_t`), the default type for `number_unsigned_t` is `std::uint64_t`. With the default values for *NumberUnsignedType* (`std::uint64_t`), the default value for `number_unsigned_t` is `std::uint64_t`.
With the default values for *NumberFloatType* (`#!cpp double`), the default type for `number_float_t` is `#!cpp double`. With the default values for *NumberFloatType* (`#!cpp double`), the default value for `number_float_t` is `#!cpp double`.
### Default behavior ### Default behavior
@@ -281,7 +281,7 @@ As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_
> This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision. > This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision.
This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default (see *NumberFloatType* above). Note that with the default `#!cpp double`, numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`. This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`.
### Storage ### Storage
@@ -704,7 +704,7 @@ inline void float_from_bits(std::uint64_t bits, float& value) noexcept
{ {
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits"); static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
const auto bits32 = static_cast<std::uint32_t>(bits); const auto bits32 = static_cast<std::uint32_t>(bits);
std::memcpy(&value, &bits32, sizeof(value)); std::memcpy(&value, &bits32, sizeof(bits32));
} }
/// the powers of ten that are exact in binary64 (up to 10^22) /// the powers of ten that are exact in binary64 (up to 10^22)
@@ -280,11 +280,6 @@ 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));
@@ -299,18 +294,6 @@ 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:
@@ -355,12 +338,6 @@ 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:
@@ -414,18 +391,6 @@ 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:
@@ -519,7 +484,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))
{ {
throw_compare_different_values(); JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
} }
// 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
@@ -528,16 +493,6 @@ 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:
+117 -161
View File
@@ -720,34 +720,6 @@ 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
@@ -803,8 +775,7 @@ private:
} }
} }
// deallocates the array/object held by v (empty, or holding only scalars // deallocates the (already empty) array/object held by v; this is
// 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
@@ -812,6 +783,7 @@ 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);
@@ -819,6 +791,7 @@ 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);
@@ -894,17 +867,9 @@ 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)
{ {
const bool fresh_now = fresh; if (has_no_children(cur))
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)
{ {
@@ -943,7 +908,6 @@ 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;
} }
} }
@@ -6914,11 +6878,12 @@ 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: for each member of source (in order), its value in /// objects: the keys common to both, in source's order
/// target, or nullptr if target has no such key; `index` is the next std::vector<typename object_t::key_type> common_keys{}; // NOLINT(readability-redundant-member-init)
std::vector<const basic_json*> target_values{}; // NOLINT(readability-redundant-member-init) /// objects: the next entry of common_keys
/// objects: the members only target has, in target's order std::size_t next_common = 0;
std::vector<const_iterator> added{}; // NOLINT(readability-redundant-member-init) /// objects: the "add" operations for keys only target has
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
@@ -6974,113 +6939,111 @@ 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 in @a target_values, for each member @a target has come after them, stores the keys common to both in
of source in order, its value in target (nullptr for a key target does not source's order in @a common_keys, stores the "add" operations for the keys
have), stores the members only target has in @a added, and returns true: only @a target has in @a added_ops, and returns true: the caller then diffs
the caller then diffs the objects member by member. Otherwise, appends the objects member by member. Otherwise, appends operations that remove
operations that remove every member of @a source and add every member of every member of @a source and add every member of @a target to @a result,
@a target to @a result, and returns false. 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<const basic_json*>& target_values, const string_t& path, std::vector<typename object_t::key_type>& common_keys,
std::vector<const_iterator>& added) basic_json& added_ops)
{ {
// first pass: one lookup per source key // first pass: record, for every source key, whether it is
target_values.reserve(source.size()); // common to both objects (in source's iteration order) or
std::size_t common = 0; // was deleted (i.e., in source but not in target) -- this is
// 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)
{ {
const auto found = target.find(it.key()); if (target.find(it.key()) != target.end())
if (found != target.cend())
{ {
target_values.push_back(&found.value()); common_keys_source_order.push_back(it.key());
++common;
}
else
{
target_values.push_back(nullptr);
} }
} }
// second pass: find the keys only target has. An object type that // second pass: find keys that were added (i.e., in target but
// keeps its members in insertion order, such as the one backing // not in source), and record the keys common to both, in
// ordered_json, also needs the common keys to be in the same order // target's iteration order -- again a by-product of the
// in both objects, and the new keys to come after them, as patch() // source.find() call already needed to detect added keys. At
// appends a new member at the end of such an object. Any other // the same time, determine whether every added key comes
// object type places its members itself, so a member-by-member diff // after every common key in target's order (a precondition
// always reproduces target there. // 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
{
#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
bool fast_path = true; if (detail::is_ordered_map<object_t>::value)
if (detail::is_ordered_map<object_t>::value)
{
// walk the common keys of source in lockstep with target: a key
// of target that is not the next one is either new or out of order
auto src = source.cbegin();
std::size_t next = 0;
bool seen_new = false;
for (auto it = target.cbegin(); it != target.cend() && fast_path; ++it)
{
while (next < target_values.size() && target_values[next] == nullptr)
{ {
++src; common_keys_target_order.push_back(it.key());
++next; if (seen_new_key)
} {
new_keys_form_suffix = false;
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;
} }
#ifdef JSON_HEDLEY_MSVC_VERSION
#pragma warning( pop )
#endif
} }
} }
else if (target.size() != common)
// Only an object type that keeps its members in insertion
// order, such as nlohmann::ordered_map, can need reordering:
// patch() appends a new member at the end of such an object.
// Any other object type places its members itself - std::map
// in key order, a hash map in an order its operator== ignores -
// so a member-by-member diff always reproduces target there.
#ifdef JSON_HEDLEY_MSVC_VERSION
#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_source_order == common_keys_target_order && new_keys_form_suffix))
{ {
for (auto it = target.cbegin(); it != target.cend(); ++it) // fast path: order of common keys already matches (or the
{ // object_t's iteration order does not depend on
if (source.find(it.key()) == source.cend()) // insertion history), so a plain per-key diff is correct
{ // and minimal, as before
added.push_back(it); common_keys = std::move(common_keys_source_order);
} return true;
}
} }
#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
@@ -7130,14 +7093,6 @@ 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);
@@ -7160,10 +7115,7 @@ 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
if (!(source[i] == target[i])) diff_recursively(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
{
diff_unequal(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
}
++i; ++i;
} }
@@ -7175,24 +7127,25 @@ public:
case value_t::object: case value_t::object:
{ {
std::vector<const basic_json*> target_values; std::vector<typename object_t::key_type> common_keys;
std::vector<const_iterator> added; basic_json added_ops(value_t::array);
if (diff_object_keys(result, source, target, path, target_values, added)) if (diff_object_keys(result, source, target, path, common_keys, added_ops))
{ {
// fast path: target_values holds, for every member of // fast path: common_keys is, by construction, the
// source in order, its value in target -- so no further // subsequence of source's keys that are common to both
// lookup is needed. Deleted keys are interleaved here // objects, in source's iteration order -- so it can be
// too, in source's original order, to match the // walked in lockstep with `source` using a cheap key
// 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.
std::size_t i = 0; auto common_it = common_keys.cbegin();
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i) for (auto it = source.cbegin(); it != source.cend(); ++it)
{ {
if (target_values[i] != nullptr) if (common_it != common_keys.cend() && it.key() == *common_it)
{ {
if (!(it.value() == *target_values[i])) diff_recursively(result, it.value(), target[it.key()], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
{ ++common_it;
diff_unequal(result, it.value(), *target_values[i], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
}
} }
else else
{ {
@@ -7201,8 +7154,10 @@ public:
} }
} }
// append the "add" operations for brand-new keys // append the "add" ops for brand-new keys collected by
diff_added(result, added, path); // diff_object_keys -- no second source.find() per target
// key needed
result.insert(result.end(), added_ops.begin(), added_ops.end());
} }
break; break;
} }
@@ -7275,17 +7230,17 @@ public:
case value_t::object: case value_t::object:
{ {
std::vector<const basic_json*> target_values; std::vector<typename object_t::key_type> common_keys;
std::vector<const_iterator> added; basic_json added_ops(value_t::array);
if (diff_object_keys(result, s, t, current_path, target_values, added)) if (diff_object_keys(result, s, t, current_path, common_keys, added_ops))
{ {
// 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().target_values = std::move(target_values); stack.back().common_keys = std::move(common_keys);
stack.back().added = std::move(added); stack.back().added_ops = std::move(added_ops);
} }
return; return;
} }
@@ -7347,10 +7302,11 @@ public:
if (it != s->cend()) if (it != s->cend())
{ {
++stack.back().member; ++stack.back().member;
const basic_json* const target_value_ptr = stack.back().target_values[stack.back().index++]; const std::size_t next_common = stack.back().next_common;
if (target_value_ptr != nullptr) if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common])
{ {
const basic_json& target_value = *target_value_ptr; ++stack.back().next_common;
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)
@@ -7368,7 +7324,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
diff_added(result, stack.back().added, current_path); result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end());
} }
// 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
+119 -208
View File
@@ -10151,7 +10151,7 @@ inline void float_from_bits(std::uint64_t bits, float& value) noexcept
{ {
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits"); static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
const auto bits32 = static_cast<std::uint32_t>(bits); const auto bits32 = static_cast<std::uint32_t>(bits);
std::memcpy(&value, &bits32, sizeof(value)); std::memcpy(&value, &bits32, sizeof(bits32));
} }
/// the powers of ten that are exact in binary64 (up to 10^22) /// the powers of ten that are exact in binary64 (up to 10^22)
@@ -19929,11 +19929,6 @@ 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));
@@ -19948,18 +19943,6 @@ 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:
@@ -20004,12 +19987,6 @@ 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:
@@ -20063,18 +20040,6 @@ 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:
@@ -20168,7 +20133,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))
{ {
throw_compare_different_values(); JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
} }
// 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
@@ -20177,16 +20142,6 @@ 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:
@@ -30113,34 +30068,6 @@ 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
@@ -30196,8 +30123,7 @@ private:
} }
} }
// deallocates the array/object held by v (empty, or holding only scalars // deallocates the (already empty) array/object held by v; this is
// 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
@@ -30205,6 +30131,7 @@ 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);
@@ -30212,6 +30139,7 @@ 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);
@@ -30287,17 +30215,9 @@ 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)
{ {
const bool fresh_now = fresh; if (has_no_children(cur))
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)
{ {
@@ -30336,7 +30256,6 @@ 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;
} }
} }
@@ -36307,11 +36226,12 @@ 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: for each member of source (in order), its value in /// objects: the keys common to both, in source's order
/// target, or nullptr if target has no such key; `index` is the next std::vector<typename object_t::key_type> common_keys{}; // NOLINT(readability-redundant-member-init)
std::vector<const basic_json*> target_values{}; // NOLINT(readability-redundant-member-init) /// objects: the next entry of common_keys
/// objects: the members only target has, in target's order std::size_t next_common = 0;
std::vector<const_iterator> added{}; // NOLINT(readability-redundant-member-init) /// objects: the "add" operations for keys only target has
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
@@ -36367,113 +36287,111 @@ 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 in @a target_values, for each member @a target has come after them, stores the keys common to both in
of source in order, its value in target (nullptr for a key target does not source's order in @a common_keys, stores the "add" operations for the keys
have), stores the members only target has in @a added, and returns true: only @a target has in @a added_ops, and returns true: the caller then diffs
the caller then diffs the objects member by member. Otherwise, appends the objects member by member. Otherwise, appends operations that remove
operations that remove every member of @a source and add every member of every member of @a source and add every member of @a target to @a result,
@a target to @a result, and returns false. 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<const basic_json*>& target_values, const string_t& path, std::vector<typename object_t::key_type>& common_keys,
std::vector<const_iterator>& added) basic_json& added_ops)
{ {
// first pass: one lookup per source key // first pass: record, for every source key, whether it is
target_values.reserve(source.size()); // common to both objects (in source's iteration order) or
std::size_t common = 0; // was deleted (i.e., in source but not in target) -- this is
// 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)
{ {
const auto found = target.find(it.key()); if (target.find(it.key()) != target.end())
if (found != target.cend())
{ {
target_values.push_back(&found.value()); common_keys_source_order.push_back(it.key());
++common;
}
else
{
target_values.push_back(nullptr);
} }
} }
// second pass: find the keys only target has. An object type that // second pass: find keys that were added (i.e., in target but
// keeps its members in insertion order, such as the one backing // not in source), and record the keys common to both, in
// ordered_json, also needs the common keys to be in the same order // target's iteration order -- again a by-product of the
// in both objects, and the new keys to come after them, as patch() // source.find() call already needed to detect added keys. At
// appends a new member at the end of such an object. Any other // the same time, determine whether every added key comes
// object type places its members itself, so a member-by-member diff // after every common key in target's order (a precondition
// always reproduces target there. // 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
{
#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
bool fast_path = true; if (detail::is_ordered_map<object_t>::value)
if (detail::is_ordered_map<object_t>::value)
{
// walk the common keys of source in lockstep with target: a key
// of target that is not the next one is either new or out of order
auto src = source.cbegin();
std::size_t next = 0;
bool seen_new = false;
for (auto it = target.cbegin(); it != target.cend() && fast_path; ++it)
{
while (next < target_values.size() && target_values[next] == nullptr)
{ {
++src; common_keys_target_order.push_back(it.key());
++next; if (seen_new_key)
} {
new_keys_form_suffix = false;
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;
} }
#ifdef JSON_HEDLEY_MSVC_VERSION
#pragma warning( pop )
#endif
} }
} }
else if (target.size() != common)
// Only an object type that keeps its members in insertion
// order, such as nlohmann::ordered_map, can need reordering:
// patch() appends a new member at the end of such an object.
// Any other object type places its members itself - std::map
// in key order, a hash map in an order its operator== ignores -
// so a member-by-member diff always reproduces target there.
#ifdef JSON_HEDLEY_MSVC_VERSION
#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_source_order == common_keys_target_order && new_keys_form_suffix))
{ {
for (auto it = target.cbegin(); it != target.cend(); ++it) // fast path: order of common keys already matches (or the
{ // object_t's iteration order does not depend on
if (source.find(it.key()) == source.cend()) // insertion history), so a plain per-key diff is correct
{ // and minimal, as before
added.push_back(it); common_keys = std::move(common_keys_source_order);
} return true;
}
} }
#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
@@ -36523,14 +36441,6 @@ 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);
@@ -36553,10 +36463,7 @@ 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
if (!(source[i] == target[i])) diff_recursively(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
{
diff_unequal(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
}
++i; ++i;
} }
@@ -36568,24 +36475,25 @@ public:
case value_t::object: case value_t::object:
{ {
std::vector<const basic_json*> target_values; std::vector<typename object_t::key_type> common_keys;
std::vector<const_iterator> added; basic_json added_ops(value_t::array);
if (diff_object_keys(result, source, target, path, target_values, added)) if (diff_object_keys(result, source, target, path, common_keys, added_ops))
{ {
// fast path: target_values holds, for every member of // fast path: common_keys is, by construction, the
// source in order, its value in target -- so no further // subsequence of source's keys that are common to both
// lookup is needed. Deleted keys are interleaved here // objects, in source's iteration order -- so it can be
// too, in source's original order, to match the // walked in lockstep with `source` using a cheap key
// 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.
std::size_t i = 0; auto common_it = common_keys.cbegin();
for (auto it = source.cbegin(); it != source.cend(); ++it, ++i) for (auto it = source.cbegin(); it != source.cend(); ++it)
{ {
if (target_values[i] != nullptr) if (common_it != common_keys.cend() && it.key() == *common_it)
{ {
if (!(it.value() == *target_values[i])) diff_recursively(result, it.value(), target[it.key()], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
{ ++common_it;
diff_unequal(result, it.value(), *target_values[i], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
}
} }
else else
{ {
@@ -36594,8 +36502,10 @@ public:
} }
} }
// append the "add" operations for brand-new keys // append the "add" ops for brand-new keys collected by
diff_added(result, added, path); // diff_object_keys -- no second source.find() per target
// key needed
result.insert(result.end(), added_ops.begin(), added_ops.end());
} }
break; break;
} }
@@ -36668,17 +36578,17 @@ public:
case value_t::object: case value_t::object:
{ {
std::vector<const basic_json*> target_values; std::vector<typename object_t::key_type> common_keys;
std::vector<const_iterator> added; basic_json added_ops(value_t::array);
if (diff_object_keys(result, s, t, current_path, target_values, added)) if (diff_object_keys(result, s, t, current_path, common_keys, added_ops))
{ {
// 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().target_values = std::move(target_values); stack.back().common_keys = std::move(common_keys);
stack.back().added = std::move(added); stack.back().added_ops = std::move(added_ops);
} }
return; return;
} }
@@ -36740,10 +36650,11 @@ public:
if (it != s->cend()) if (it != s->cend())
{ {
++stack.back().member; ++stack.back().member;
const basic_json* const target_value_ptr = stack.back().target_values[stack.back().index++]; const std::size_t next_common = stack.back().next_common;
if (target_value_ptr != nullptr) if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common])
{ {
const basic_json& target_value = *target_value_ptr; ++stack.back().next_common;
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)
@@ -36761,7 +36672,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
diff_added(result, stack.back().added, current_path); result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end());
} }
// 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