Skip to content

Commit

Permalink
Add deepEqual skip and deepFunction. Closes #319
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Jul 24, 2019
1 parent 68762f5 commit ebd9b2b
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 8 deletions.
19 changes: 16 additions & 3 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,23 @@ const config = Hoek.applyToDefaults(defaults, source, { shallow: ['db.server'] }
const config = Hoek.applyToDefaults(defaults, source, { shallow: [['db', 'server']] }); // results in { db: { server: { port: 8080 }, name: 'example' } }
```

#### deepEqual(b, a, [options])
#### deepEqual(a, b, [options])

Performs a deep comparison of the two values including support for circular dependencies, prototype, and enumerable properties.
To skip prototype comparisons, use `options.prototype = false` and to exclude symbols, used `options.symbols = false`.
Performs a deep comparison of the two values including support for circular dependencies,
prototype, and enumerable properties, where:
- `a` - the first value.
- `b` - the second value.
- `options` - optional settings:
- `deepFunction` - when `true`, function values are deep compared using their source code and
object properties. Defaults to `false`.
- `part` - when `true`, allows a partial match where some of `b` is present in `a`. Defaults to
`false`.
- `prototype` - when `false, prototype comparisons are skipped. Defaults to `true`.
- `skip` - an array of key name strings to skip comparing. The keys can be found in any level
of the object. Note that both values must contain the key - only the value comparison is
skipped. Only applies to plain objects and deep functions (not to map, sets, etc.). Defaults
to no skipping.
- `symbols` - when `false`, symbol properties are ignored. Defaults to `true`.

```javascript
Hoek.deepEqual({ a: [1, 2], b: 'string', c: { d: true } }, { a: [1, 2], b: 'string', c: { d: true } }); //results in true
Expand Down
27 changes: 23 additions & 4 deletions lib/deep-equal.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,22 @@ internals.isDeepEqual = function (obj, ref, options, seen) {
return false;
}

if (type !== 'object' ||
obj === null ||
if (obj === null ||
ref === null) {

return false;
}

if (type === 'function') {
if (!options.deepFunction ||
obj.toString() !== ref.toString()) {

return false;
}

// Continue as object
}
else if (type !== 'object') {
return obj !== obj && ref !== ref; // NaN
}

Expand Down Expand Up @@ -222,11 +234,16 @@ internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {

for (let i = 0; i < objKeys.length; ++i) {
const key = objKeys[i];

if (!hasOwnEnumerableProperty(ref, key)) {
return false;
}

if (options.skip &&
options.skip.includes(key)) {

continue;
}

if (!isDeepEqual(obj[key], ref[key], options, seen)) {
return false;
}
Expand All @@ -246,7 +263,9 @@ internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {
return false;
}

if (!isDeepEqual(obj[key], ref[key], options, seen)) {
if (!(options.skip && options.skip.includes(key)) &&
!isDeepEqual(obj[key], ref[key], options, seen)) {

return false;
}
}
Expand Down
14 changes: 14 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ declare namespace deepEqual {

interface Options {

/**
Compare functions with difference references by comparing their internal code and properties.
@default false
*/
readonly deepFunction?: boolean;

/**
Allow partial match.
Expand All @@ -26,6 +33,13 @@ declare namespace deepEqual {
*/
readonly prototype?: boolean;

/**
List of object keys to ignore different values of.
@default null
*/
readonly skip?: (string | symbol)[];

/**
Compare symbol properties.
Expand Down
35 changes: 35 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1698,6 +1698,41 @@ describe('deepEqual()', () => {
expect(Hoek.deepEqual({ foo: undefined }, { bar: undefined })).to.be.false();
});

it('compares functions', () => {

const f1 = () => 1;
const f2 = () => 2;
const f2a = () => 2;

expect(Hoek.deepEqual({ f1 }, { f1 })).to.be.true();
expect(Hoek.deepEqual({ f1 }, { f1: f2 })).to.be.false();
expect(Hoek.deepEqual({ f2 }, { f2: f2a })).to.be.false();
expect(Hoek.deepEqual({ f2 }, { f2: f2a }, { deepFunction: true })).to.be.true();
expect(Hoek.deepEqual({ f2 }, { f2: f1 }, { deepFunction: true })).to.be.false();

const f3 = () => 3;
f3.x = 1;

const f3a = () => 3;
f3a.x = 1;

const f3b = () => 3;
f3b.x = 2;

expect(Hoek.deepEqual({ f3 }, { f3: f3a }, { deepFunction: true })).to.be.true();
expect(Hoek.deepEqual({ f3 }, { f3: f3b }, { deepFunction: true })).to.be.false();
});

it('skips keys', () => {

expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 })).to.be.false();
expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 }, { skip: ['c'] })).to.be.true();

const sym = Symbol('test');
expect(Hoek.deepEqual({ a: 1, b: 2, [sym]: 3 }, { a: 1, b: 2, [sym]: 4 })).to.be.false();
expect(Hoek.deepEqual({ a: 1, b: 2, [sym]: 3 }, { a: 1, b: 2, [sym]: 4 }, { skip: [sym] })).to.be.true();
});

it('handles circular dependency', () => {

const a = {};
Expand Down
4 changes: 3 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ interface Bar {
Hoek.deepEqual('some', 'some');
Hoek.deepEqual('some', 3);
Hoek.deepEqual({}, {});
Hoek.deepEqual({}, {}, { prototype: false, symbols: true, part: false });
Hoek.deepEqual({}, {}, { prototype: false, symbols: true, part: false, deepFunction: true });
Hoek.deepEqual({}, {}, { skip: ['a', 'b', Symbol('test')]});

expect.type<boolean>(Hoek.deepEqual(1, 2));

expect.error(Hoek.deepEqual());
expect.error(Hoek.deepEqual(1, 2, {}, 'x'));
expect.error(Hoek.deepEqual({}, {}, { unknown: true }));
expect.error(Hoek.deepEqual({}, {}, { skip: [1] }))


// clone()
Expand Down

0 comments on commit ebd9b2b

Please sign in to comment.