Stop using deep-strict-equal algorithms to verify deep copies because they don't detect
deep equivalency. Deep-equivalency, not deep-strict-equality, describes deep-copies. This package's
deepEquivalent()
function should be your tool for testing deep copies and debugging deep copying algorithms.
The reason you want this package is for its correctness, as proven by extensive testing, its speed,
and its concise reporting of why deep-equivalency fails when it does. deepEquivalent()
,
has no peers on NPM as far as I can tell. deepEquivalent()
is 3-5 times faster than some deep-strict-equal algorithms,
including assert.deepStrictEqual()
, which don't have as much work to do, while the deep-strict-equal algorithms
that go faster tend to be very incorrect, having cut too many corners.
deepEquivalent(x,y)
catches every possible way in which x
and y
may not be
deep copies of each other. It takes into account internal prototypes, circular references,
duplicate references, functions, getters/setters, property descriptors, and frozen/sealed/extensible states.
Some deep copiers on NPM will copy functions, Booleans, etc., as is. deepEquivalent(x,y)
can handle that:
see Usage below.
npm install deep-equal-diagnostics
const {deepEquivalent} = require('deep-copy-diagnostics');
if(deepEquivalent(x,y))
{
// statement that x and y are deeply equivalent
// (that x and y are deep copies of each other)
console.log(deepEquivalent.message);
// x and y are deep copies of each other.
// By the way, corresponding functions must not be
// reference equal because deep copies don't share
// state. But read below to see how to relax this
// requirement.
}
else
{
// Very clear and precise reason why x and y are
// not deeply equivalent.
console.log(deepEquivalent.message);
// Examine the two subobjects that were
// not equivalent if you want.
const {source, target} = deepEquivalent.pair;
}
if(deepEquivalent(x,y, new Set([Function, Boolean])
{
console.log(deepEquivalent.message);
// statement that x and y are deeply equivalent
// except that corresponding functions are expected to
// be reference-equal, and corresponding Booleans are
// expected to be reference-equal.
}
else
{
// Very clear and precise reason why x and y are not
// deeply equivalent, with above noted exceptions
// enforced.
console.log(deepEquivalent.message);
// Examine the two subobjects that were
// not equivalent or the two subobjects that
// were expected to be reference-equal but
// were not.
const {source, target} = deepEquivalent.pair;
}
export | description |
---|---|
deepEquivalent(x,y, primitives) |
tests for deep equivalence. primitives is a set of classes whose instances are to be reference equal: see Usage |
See the Tests folder.
Two objects can pass the deep-strict-equal test yet not be deep copies of each other. This can happen in at least two ways as demonstrated below.
Example 1
const A = new Date();
const x = {a:A};
const y = {a:A}
assert.deepStrictEqual(x,y); // Says all is good
// No exception thrown.
x and y are not deep copies because they share the 'a'
property, even though it passes the deepStrictEqual
test.
Example 2
const x = {a:{b:{c:1}}}
const y = {a:{b:{c:1}}}
x.a.b.d = x
y.a.b.d = x
assert.deepStrictEqual(A,B); // Says all is good
// No exception thrown.
x and y are not deep copies because they have a
circular reference in different parts of the object
trees, even though it passes the deepStrictEqual
test.
Read this section only if you want.
The elements x
and y
are deeply equivalent if the elements in their trees are in a one-one correspondence
that meets the following conditions.
- x corresponds to y
- Corresponding elements must have the same type (*).
- Corresponding objects must have the same internal prototype.
- Corresponding primitives must have the same value.
- Corresponding WeakMaps or WeakSets must be reference equal.
- Corresponding Booleans, Numbers, Strings, Dates, RegExps must have the same internal state, i.e., valueOfs() or toStrings() must be the same.
- Corresponding Functions as strings must be the same.
- Corresponding objects are never reference equal (except for WeakSets and WeakMaps).
- Corresponding objects must have the same frozen/sealed/extensible states.
- If p and q are corresponding objects
- if either p.a or q.a exist then both exist, and they correspond. Moreover, the property descriptors must be the same.
- if p is a Set (we know by 2 that q is a Set) then insertion order defines the one-one correspondence between their members.
- if p is a Map (we know by 2 that q is a Map) then insertion order defines the one-one correspondences between their keys and their values.
It would be easy for deepEquivalent()
to use the theoretical definition that says insertion
order does not matter for Sets and Maps. However, it would be perverse for a deep copier to jumble
the insertion order up. For example, when copying a Set, a deep copier will read its members
in insertion order, and then copy them in insertion order.
(*) As determined by the dtype(x)
function of the type-quickly package.
Version | Published | |
---|---|---|
1.0.0 | 4-27-2022 | deep copy diagnostic testing |
1.0.1 | 4-28-2022 | corrected typo $path[mapKey[${n}]] --> ${path}[mapKey[${n}]]
|
1.0.2 | 5-4-2022 | Fixed: did not examine buffer properties of Typed Arrays for circularity/duplicity. New tests added to show its now correct. |