A blazing fast equality comparison, either shallow or deep
Installations
npm install fast-equals
Releases
Release 5.0.1
Published on 18 Mar 2023
Release 5.0.0
Published on 05 Mar 2023
Release 5.0.0-beta.6
Published on 28 Feb 2023
Release 5.0.0-beta.5
Published on 26 Feb 2023
Release 5.0.0-beta.4
Published on 26 Feb 2023
Release 5.0.0-beta.3
Published on 21 Feb 2023
Developer
planttheidea
Developer Guide
Module System
ESM, UMD
Min. Node Version
>=6.0.0
Typescript Support
Yes
Node Version
16.14.2
NPM Version
8.7.0
Statistics
477 Stars
240 Commits
20 Forks
7 Watching
11 Branches
9 Contributors
Updated on 28 Nov 2024
Bundle Size
5.01 kB
Minified
1.78 kB
Minified + Gzipped
Languages
TypeScript (70.12%)
JavaScript (29.88%)
Total Downloads
Cumulative downloads
Total Downloads
311,875,248
Last day
-3.2%
696,551
Compared to previous day
Last week
3.6%
3,880,220
Compared to previous week
Last month
8.2%
16,328,377
Compared to previous month
Last year
67.1%
146,265,779
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dev Dependencies
42
fast-equals
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:
- Plain objects (including
react
elements andArguments
) - Arrays
- Typed Arrays
Date
objectsRegExp
objectsMap
/Set
iterablesPromise
objects- Primitive wrappers (
new Boolean()
/new Number()
/new String()
) - Custom class instances, including subclasses of native classes
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.
Table of contents
Usage
1import { deepEqual } from 'fast-equals'; 2 3console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true
Specific builds
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:
- ESM =>
fast-equals/dist/esm/index.mjs
- CommonJS =>
fast-equals/dist/cjs/index.cjs
- UMD =>
fast-equals/dist/umd/index.js
- Minified UMD =>
fast-equals/dist/min/index.js
If you are having issues loading a specific build type, please file an issue.
Available methods
deepEqual
Performs a deep equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
1import { deepEqual } from 'fast-equals'; 2 3const objectA = { foo: { bar: 'baz' } }; 4const objectB = { foo: { bar: 'baz' } }; 5 6console.log(objectA === objectB); // false 7console.log(deepEqual(objectA, objectB)); // true
Comparing Map
s
Map
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
:
1const mapA = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]); 2const mapB = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]); 3 4deepEqual(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
.
shallowEqual
Performs a shallow equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
1import { shallowEqual } from 'fast-equals'; 2 3const nestedObject = { bar: 'baz' }; 4 5const objectA = { foo: nestedObject }; 6const objectB = { foo: nestedObject }; 7const objectC = { foo: { bar: 'baz' } }; 8 9console.log(objectA === objectB); // false 10console.log(shallowEqual(objectA, objectB)); // true 11console.log(shallowEqual(objectA, objectC)); // false
sameValueZeroEqual
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
.
1import { sameValueZeroEqual } from 'fast-equals'; 2 3const mainObject = { foo: NaN, bar: 'baz' }; 4 5const objectA = 'baz'; 6const objectB = NaN; 7const objectC = { foo: NaN, bar: 'baz' }; 8 9console.log(sameValueZeroEqual(mainObject.bar, objectA)); // true 10console.log(sameValueZeroEqual(mainObject.foo, objectB)); // true 11console.log(sameValueZeroEqual(mainObject, objectC)); // false
circularDeepEqual
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.
1function Circular(value) {
2 this.me = {
3 deeply: {
4 nested: {
5 reference: this,
6 },
7 },
8 value,
9 };
10}
11
12console.log(circularDeepEqual(new Circular('foo'), new Circular('foo'))); // true
13console.log(circularDeepEqual(new Circular('foo'), new Circular('bar'))); // false
Just as with deepEqual
, both keys and values are compared for deep equality.
circularShallowEqual
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.
1const array = ['foo']; 2 3array.push(array); 4 5console.log(circularShallowEqual(array, ['foo', array])); // true 6console.log(circularShallowEqual(array, [array])); // false
strictDeepEqual
Performs the same comparison as deepEqual
but performs a strict comparison of the objects. In this includes:
- Checking symbol properties
- Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on
Map
/Set
objects
1const array = [{ foo: 'bar' }]; 2const otherArray = [{ foo: 'bar' }]; 3 4array.bar = 'baz'; 5otherArray.bar = 'baz'; 6 7console.log(strictDeepEqual(array, otherArray)); // true; 8console.log(strictDeepEqual(array, [{ foo: 'bar' }])); // false;
strictShallowEqual
Performs the same comparison as shallowEqual
but performs a strict comparison of the objects. In this includes:
- Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on
Map
/Set
objects
1const array = ['foo']; 2const otherArray = ['foo']; 3 4array.bar = 'baz'; 5otherArray.bar = 'baz'; 6 7console.log(strictDeepEqual(array, otherArray)); // true; 8console.log(strictDeepEqual(array, ['foo'])); // false;
strictCircularDeepEqual
Performs the same comparison as circularDeepEqual
but performs a strict comparison of the objects. In this includes:
- Checking
Symbol
properties on the object - Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on
Map
/Set
objects
1function Circular(value) {
2 this.me = {
3 deeply: {
4 nested: {
5 reference: this,
6 },
7 },
8 value,
9 };
10}
11
12const first = new Circular('foo');
13
14Object.defineProperty(first, 'bar', {
15 enumerable: false,
16 value: 'baz',
17});
18
19const second = new Circular('foo');
20
21Object.defineProperty(second, 'bar', {
22 enumerable: false,
23 value: 'baz',
24});
25
26console.log(circularDeepEqual(first, second)); // true
27console.log(circularDeepEqual(first, new Circular('foo'))); // false
strictCircularShallowEqual
Performs the same comparison as circularShallowEqual
but performs a strict comparison of the objects. In this includes:
- Checking non-enumerable properties in object comparisons
- Checking full descriptor of properties on the object to match
- Checking non-index properties on arrays
- Checking non-key properties on
Map
/Set
objects
1const array = ['foo']; 2const otherArray = ['foo']; 3 4array.push(array); 5otherArray.push(otherArray); 6 7array.bar = 'baz'; 8otherArray.bar = 'baz'; 9 10console.log(circularShallowEqual(array, otherArray)); // true 11console.log(circularShallowEqual(array, ['foo', array])); // false
createCustomEqual
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:
1interface Cache<Key extends object, Value> { 2 delete(key: Key): boolean; 3 get(key: Key): Value | undefined; 4 set(key: Key, value: any): any; 5} 6 7interface ComparatorConfig<Meta> { 8 areArraysEqual: TypeEqualityComparator<any[], Meta>; 9 areDatesEqual: TypeEqualityComparator<Date, Meta>; 10 areMapsEqual: TypeEqualityComparator<Map<any, any>, Meta>; 11 areObjectsEqual: TypeEqualityComparator<Record<string, any>, Meta>; 12 arePrimitiveWrappersEqual: TypeEqualityComparator< 13 boolean | string | number, 14 Meta 15 >; 16 areRegExpsEqual: TypeEqualityComparator<RegExp, Meta>; 17 areSetsEqual: TypeEqualityComparator<Set<any>, Meta>; 18 areTypedArraysEqual: TypeEqualityComparatory<TypedArray, Meta>; 19} 20 21function createCustomEqual<Meta>(options: { 22 circular?: boolean; 23 createCustomConfig?: ( 24 defaultConfig: ComparatorConfig<Meta>, 25 ) => Partial<ComparatorConfig<Meta>>; 26 createInternalComparator?: ( 27 compare: <A, B>(a: A, b: B, state: State<Meta>) => boolean, 28 ) => ( 29 a: any, 30 b: any, 31 indexOrKeyA: any, 32 indexOrKeyB: any, 33 parentA: any, 34 parentB: any, 35 state: State<Meta>, 36 ) => boolean; 37 createState?: () => { cache?: Cache; meta?: Meta }; 38 strict?: boolean; 39}): <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.
Recipes
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.
- Legacy environment support for
RegExp
comparators - Explicit property check
- Using
meta
in comparison - Comparing non-standard properties
- Strict property descriptor comparison
- Legacy environment support for circualr equal comparators
Benchmarks
All 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:
- Primitives (
String
,Number
,null
,undefined
) Function
Object
Array
Date
RegExp
react
elements- A mixed object with a combination of all the above types
1Testing mixed objects equal... 2┌─────────┬─────────────────────────────────┬────────────────┐ 3│ (index) │ Package │ Ops/sec │ 4├─────────┼─────────────────────────────────┼────────────────┤ 5│ 0 │ 'fast-equals' │ 1249567.730326 │ 6│ 1 │ 'fast-deep-equal' │ 1182463.587514 │ 7│ 2 │ 'react-fast-compare' │ 1152487.319161 │ 8│ 3 │ 'shallow-equal-fuzzy' │ 1092360.712389 │ 9│ 4 │ 'fast-equals (circular)' │ 676669.92003 │ 10│ 5 │ 'underscore.isEqual' │ 429430.837497 │ 11│ 6 │ 'lodash.isEqual' │ 237915.684734 │ 12│ 7 │ 'fast-equals (strict)' │ 181386.38032 │ 13│ 8 │ 'fast-equals (strict circular)' │ 156779.745875 │ 14│ 9 │ 'deep-eql' │ 139155.099209 │ 15│ 10 │ 'deep-equal' │ 1026.527229 │ 16└─────────┴─────────────────────────────────┴────────────────┘ 17 18Testing mixed objects not equal... 19┌─────────┬─────────────────────────────────┬────────────────┐ 20│ (index) │ Package │ Ops/sec │ 21├─────────┼─────────────────────────────────┼────────────────┤ 22│ 0 │ 'fast-equals' │ 3255824.097237 │ 23│ 1 │ 'react-fast-compare' │ 2654721.726058 │ 24│ 2 │ 'fast-deep-equal' │ 2582218.974752 │ 25│ 3 │ 'fast-equals (circular)' │ 2474303.26566 │ 26│ 4 │ 'fast-equals (strict)' │ 1088066.604881 │ 27│ 5 │ 'fast-equals (strict circular)' │ 949253.614181 │ 28│ 6 │ 'nano-equal' │ 939170.554148 │ 29│ 7 │ 'underscore.isEqual' │ 738852.197879 │ 30│ 8 │ 'lodash.isEqual' │ 307306.622212 │ 31│ 9 │ 'deep-eql' │ 156250.110401 │ 32│ 10 │ 'assert.deepStrictEqual' │ 22839.454561 │ 33│ 11 │ 'deep-equal' │ 4034.45114 │ 34└─────────┴─────────────────────────────────┴────────────────┘
Caveats that impact the benchmark (and accuracy of comparison):
Map
s,Promise
s, andSet
s were excluded from the benchmark entirely because no library other thandeep-eql
fully supported their comparisonfast-deep-equal
,react-fast-compare
andnano-equal
throw on objects withnull
as prototype (Object.create(null)
)assert.deepStrictEqual
does not supportNaN
orSameValueZero
equality for datesdeep-eql
does not supportSameValueZero
equality for zero equality (positive and negative zero are not equal)deep-equal
does not supportNaN
and does not strictly compare object type, or date / regexp values, nor usesSameValueZero
equality for datesfast-deep-equal
does not supportNaN
orSameValueZero
equality for datesnano-equal
does not strictly compare object property structure, array length, or object type, norSameValueZero
equality for datesreact-fast-compare
does not supportNaN
orSameValueZero
equality for dates, and does not comparefunction
equalityshallow-equal-fuzzy
does not strictly compare object type or regexp values, norSameValueZero
equality for datesunderscore.isEqual
does not supportSameValueZero
equality for primitives or dates
All 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.
Development
Standard practice, clone the repo and npm i
to get the dependencies. The following npm scripts are available:
- benchmark => run benchmark tests against other equality libraries
- build => build
main
,module
, andbrowser
distributables withrollup
- clean => run
rimraf
on thedist
folder - dev => start webpack playground App
- dist => run
build
- lint => run ESLint on all files in
src
folder (also runs ondev
script) - lint:fix => run
lint
script, but with auto-fixer - prepublish:compile => run
lint
,test:coverage
,transpile:lib
,transpile:es
, anddist
scripts - start => run
dev
- test => run AVA with NODE_ENV=test on all files in
test
folder - test:coverage => run same script as
test
with code coverage calculation vianyc
- test:watch => run same script as
test
but keep persistent watcher
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
0 commit(s) and 7 issue activity found in the last 90 days -- score normalized to 5
Reason
Found 1/15 approved changesets -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 22 are checked with a SAST tool
Reason
21 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc
- Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx
- Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp
- Warn: Project is vulnerable to: GHSA-c7qv-q95q-8v27
- Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22
- Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j
- Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm
- Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw
- Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg
- Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p
- Warn: Project is vulnerable to: GHSA-cchq-frgv-rjh5
- Warn: Project is vulnerable to: GHSA-g644-9gfx-q4q4
- Warn: Project is vulnerable to: GHSA-4vvj-4cpr-p986
- Warn: Project is vulnerable to: GHSA-wr3j-pwj9-hqq6
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
Score
2.8
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More