Perform blazing fast equality comparisons (either deep or shallow) on two objects passed, while also maintaining a high degree of flexibility for various implementation use-cases. It has no dependencies, and is ~1.8kB when minified and gzipped.
The following types are handled out-of-the-box:
react
elements and Arguments
)Date
objectsRegExp
objectsMap
/ Set
iterablesPromise
objectsnew Boolean()
/ new Number()
/ new String()
)Methods are available for deep, shallow, or referential equality comparison. In addition, you can opt into support for circular objects, or performing a "strict" comparison with unconventional property definition, or both. You can also customize any specific type comparison based on your application's use-cases.
import { deepEqual } from 'fast-equals';
console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true
By default, npm should resolve the correct build of the package based on your consumption (ESM vs CommonJS). However, if you want to force use of a specific build, they can be located here:
fast-equals/dist/esm/index.mjs
fast-equals/dist/cjs/index.cjs
fast-equals/dist/umd/index.js
fast-equals/dist/min/index.js
If you are having issues loading a specific build type, please file an issue.
Performs a deep equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
import { deepEqual } from 'fast-equals';
const objectA = { foo: { bar: 'baz' } };
const objectB = { foo: { bar: 'baz' } };
console.log(objectA === objectB); // false
console.log(deepEqual(objectA, objectB)); // true
Map
sMap
objects support complex keys (objects, Arrays, etc.), however the spec for key lookups in Map
are based on SameZeroValue
. If the spec were followed for comparison, the following would always be false
:
const mapA = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
const mapB = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
deepEqual(mapA, mapB);
To support true deep equality of all contents, fast-equals
will perform a deep equality comparison for key and value parirs. Therefore, the above would be true
.
Performs a shallow equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
import { shallowEqual } from 'fast-equals';
const nestedObject = { bar: 'baz' };
const objectA = { foo: nestedObject };
const objectB = { foo: nestedObject };
const objectC = { foo: { bar: 'baz' } };
console.log(objectA === objectB); // false
console.log(shallowEqual(objectA, objectB)); // true
console.log(shallowEqual(objectA, objectC)); // false
Performs a SameValueZero
comparison on the two objects passed and returns a boolean representing the value equivalency of the objects. In simple terms, this means either strictly equal or both NaN
.
import { sameValueZeroEqual } from 'fast-equals';
const mainObject = { foo: NaN, bar: 'baz' };
const objectA = 'baz';
const objectB = NaN;
const objectC = { foo: NaN, bar: 'baz' };
console.log(sameValueZeroEqual(mainObject.bar, objectA)); // true
console.log(sameValueZeroEqual(mainObject.foo, objectB)); // true
console.log(sameValueZeroEqual(mainObject, objectC)); // false
Performs the same comparison as deepEqual
but supports circular objects. It is slower than deepEqual
, so only use if you know circular objects are present.
function Circular(value) {
this.me = {
deeply: {
nested: {
reference: this,
},
},
value,
};
}
console.log(circularDeepEqual(new Circular('foo'), new Circular('foo'))); // true
console.log(circularDeepEqual(new Circular('foo'), new Circular('bar'))); // false
Just as with deepEqual
, both keys and values are compared for deep equality.
Performs the same comparison as shallowequal
but supports circular objects. It is slower than shallowEqual
, so only use if you know circular objects are present.
const array = ['foo'];
array.push(array);
console.log(circularShallowEqual(array, ['foo', array])); // true
console.log(circularShallowEqual(array, [array])); // false
Performs the same comparison as deepEqual
but performs a strict comparison of the objects. In this includes:
Map
/ Set
objectsconst array = [{ foo: 'bar' }];
const otherArray = [{ foo: 'bar' }];
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictDeepEqual(array, otherArray)); // true;
console.log(strictDeepEqual(array, [{ foo: 'bar' }])); // false;
Performs the same comparison as shallowEqual
but performs a strict comparison of the objects. In this includes:
Map
/ Set
objectsconst array = ['foo'];
const otherArray = ['foo'];
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(strictDeepEqual(array, otherArray)); // true;
console.log(strictDeepEqual(array, ['foo'])); // false;
Performs the same comparison as circularDeepEqual
but performs a strict comparison of the objects. In this includes:
Symbol
properties on the objectMap
/ Set
objectsfunction Circular(value) {
this.me = {
deeply: {
nested: {
reference: this,
},
},
value,
};
}
const first = new Circular('foo');
Object.defineProperty(first, 'bar', {
enumerable: false,
value: 'baz',
});
const second = new Circular('foo');
Object.defineProperty(second, 'bar', {
enumerable: false,
value: 'baz',
});
console.log(circularDeepEqual(first, second)); // true
console.log(circularDeepEqual(first, new Circular('foo'))); // false
Performs the same comparison as circularShallowEqual
but performs a strict comparison of the objects. In this includes:
Map
/ Set
objectsconst array = ['foo'];
const otherArray = ['foo'];
array.push(array);
otherArray.push(otherArray);
array.bar = 'baz';
otherArray.bar = 'baz';
console.log(circularShallowEqual(array, otherArray)); // true
console.log(circularShallowEqual(array, ['foo', array])); // false
Creates a custom equality comparator that will be used on nested values in the object. Unlike deepEqual
and shallowEqual
, this is a factory method that receives the default options used internally, and allows you to override the defaults as needed. This is generally for extreme edge-cases, or supporting legacy environments.
The signature is as follows:
interface Cache<Key extends object, Value> {
delete(key: Key): boolean;
get(key: Key): Value | undefined;
set(key: Key, value: any): any;
}
interface ComparatorConfig<Meta> {
areArraysEqual: TypeEqualityComparator<any[], Meta>;
areDatesEqual: TypeEqualityComparator<Date, Meta>;
areMapsEqual: TypeEqualityComparator<Map<any, any>, Meta>;
areObjectsEqual: TypeEqualityComparator<Record<string, any>, Meta>;
arePrimitiveWrappersEqual: TypeEqualityComparator<
boolean | string | number,
Meta
>;
areRegExpsEqual: TypeEqualityComparator<RegExp, Meta>;
areSetsEqual: TypeEqualityComparator<Set<any>, Meta>;
areTypedArraysEqual: TypeEqualityComparatory<TypedArray, Meta>;
}
function createCustomEqual<Meta>(options: {
circular?: boolean;
createCustomConfig?: (
defaultConfig: ComparatorConfig<Meta>,
) => Partial<ComparatorConfig<Meta>>;
createInternalComparator?: (
compare: <A, B>(a: A, b: B, state: State<Meta>) => boolean,
) => (
a: any,
b: any,
indexOrKeyA: any,
indexOrKeyB: any,
parentA: any,
parentB: any,
state: State<Meta>,
) => boolean;
createState?: () => { cache?: Cache; meta?: Meta };
strict?: boolean;
}): <A, B>(a: A, b: B) => boolean;
Create a custom equality comparator. This allows complete control over building a bespoke equality method, in case your use-case requires a higher degree of performance, legacy environment support, or any other non-standard usage. The recipes provide examples of use in different use-cases, but if you have a specific goal in mind and would like assistance feel free to file an issue.
NOTE: Map
implementations compare equality for both keys and value. When using a custom comparator and comparing equality of the keys, the iteration index is provided as both indexOrKeyA
and indexOrKeyB
to help use-cases where ordering of keys matters to equality.
Some recipes have been created to provide examples of use-cases for createCustomEqual
. Even if not directly applicable to the problem you are solving, they can offer guidance of how to structure your solution.
RegExp
comparatorsmeta
in comparisonAll benchmarks were performed on an i9-11900H Ubuntu Linux 22.04 laptop with 64GB of memory using NodeJS version 16.14.2
, and are based on averages of running comparisons based deep equality on the following object types:
String
, Number
, null
, undefined
)Function
Object
Array
Date
RegExp
react
elementsTesting mixed objects equal...
┌─────────┬─────────────────────────────────┬────────────────┐
│ (index) │ Package │ Ops/sec │
├─────────┼─────────────────────────────────┼────────────────┤
│ 0 │ 'fast-equals' │ 1249567.730326 │
│ 1 │ 'fast-deep-equal' │ 1182463.587514 │
│ 2 │ 'react-fast-compare' │ 1152487.319161 │
│ 3 │ 'shallow-equal-fuzzy' │ 1092360.712389 │
│ 4 │ 'fast-equals (circular)' │ 676669.92003 │
│ 5 │ 'underscore.isEqual' │ 429430.837497 │
│ 6 │ 'lodash.isEqual' │ 237915.684734 │
│ 7 │ 'fast-equals (strict)' │ 181386.38032 │
│ 8 │ 'fast-equals (strict circular)' │ 156779.745875 │
│ 9 │ 'deep-eql' │ 139155.099209 │
│ 10 │ 'deep-equal' │ 1026.527229 │
└─────────┴─────────────────────────────────┴────────────────┘
Testing mixed objects not equal...
┌─────────┬─────────────────────────────────┬────────────────┐
│ (index) │ Package │ Ops/sec │
├─────────┼─────────────────────────────────┼────────────────┤
│ 0 │ 'fast-equals' │ 3255824.097237 │
│ 1 │ 'react-fast-compare' │ 2654721.726058 │
│ 2 │ 'fast-deep-equal' │ 2582218.974752 │
│ 3 │ 'fast-equals (circular)' │ 2474303.26566 │
│ 4 │ 'fast-equals (strict)' │ 1088066.604881 │
│ 5 │ 'fast-equals (strict circular)' │ 949253.614181 │
│ 6 │ 'nano-equal' │ 939170.554148 │
│ 7 │ 'underscore.isEqual' │ 738852.197879 │
│ 8 │ 'lodash.isEqual' │ 307306.622212 │
│ 9 │ 'deep-eql' │ 156250.110401 │
│ 10 │ 'assert.deepStrictEqual' │ 22839.454561 │
│ 11 │ 'deep-equal' │ 4034.45114 │
└─────────┴─────────────────────────────────┴────────────────┘
Caveats that impact the benchmark (and accuracy of comparison):
Map
s, Promise
s, and Set
s were excluded from the benchmark entirely because no library other than deep-eql
fully supported their comparisonfast-deep-equal
, react-fast-compare
and nano-equal
throw on objects with null
as prototype (Object.create(null)
)assert.deepStrictEqual
does not support NaN
or SameValueZero
equality for datesdeep-eql
does not support SameValueZero
equality for zero equality (positive and negative zero are not equal)deep-equal
does not support NaN
and does not strictly compare object type, or date / regexp values, nor uses SameValueZero
equality for datesfast-deep-equal
does not support NaN
or SameValueZero
equality for datesnano-equal
does not strictly compare object property structure, array length, or object type, nor SameValueZero
equality for datesreact-fast-compare
does not support NaN
or SameValueZero
equality for dates, and does not compare function
equalityshallow-equal-fuzzy
does not strictly compare object type or regexp values, nor SameValueZero
equality for datesunderscore.isEqual
does not support SameValueZero
equality for primitives or datesAll of these have the potential of inflating the respective library's numbers in comparison to fast-equals
, but it was the closest apples-to-apples comparison I could create of a reasonable sample size. It should be noted that react
elements can be circular objects, however simple elements are not; I kept the react
comparison very basic to allow it to be included.
Standard practice, clone the repo and npm i
to get the dependencies. The following npm scripts are available:
main
, module
, and browser
distributables with rollup
rimraf
on the dist
folderbuild
src
folder (also runs on dev
script)lint
script, but with auto-fixerlint
, test:coverage
, transpile:lib
, transpile:es
, and dist
scriptsdev
test
foldertest
with code coverage calculation via nyc
test
but keep persistent watcher