Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
fast-equals
Advanced tools
The fast-equals package is a high-performance deep equality checking library that provides functions to determine if two values are deeply equal. It is optimized for speed and can handle complex data structures, including objects, arrays, dates, and more.
deepEqual
Checks if two values are deeply equal, meaning all their nested properties are equal.
const { deepEqual } = require('fast-equals');
console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true
shallowEqual
Checks if two values are shallowly equal, meaning their immediate properties are equal without checking nested objects.
const { shallowEqual } = require('fast-equals');
console.log(shallowEqual({ foo: 'bar' }, { foo: 'bar' })); // true
circularDeepEqual
Checks if two values are deeply equal, including when they have circular references.
const { circularDeepEqual } = require('fast-equals');
const objectA = { foo: 'bar' };
objectA.self = objectA;
const objectB = { foo: 'bar' };
objectB.self = objectB;
console.log(circularDeepEqual(objectA, objectB)); // true
circularShallowEqual
Checks if two values are shallowly equal, including when they have circular references.
const { circularShallowEqual } = require('fast-equals');
const arrayA = ['foo', 'bar'];
arrayA.push(arrayA);
const arrayB = ['foo', 'bar'];
arrayB.push(arrayB);
console.log(circularShallowEqual(arrayA, arrayB)); // true
sameValueZeroEqual
Checks if two values are equal according to the SameValueZero comparison algorithm, which treats NaN as equal to NaN and -0 as equal to +0.
const { sameValueZeroEqual } = require('fast-equals');
console.log(sameValueZeroEqual(NaN, NaN)); // true
Lodash's isEqual function is a popular utility for performing deep equality checks. It is part of the larger Lodash library, which offers a wide range of utilities for working with arrays, objects, and other data types. Compared to fast-equals, lodash.isequal may be slower but is part of a well-established utility library with a large user base.
The deep-equal package provides functions for deep equality checking. It is less optimized for performance compared to fast-equals but offers a simple API for checking deep equality between two values.
Ramda is a functional programming library that includes a deep equality checking function called equals. Ramda's approach is more functional and immutable compared to fast-equals, and it is designed to work well in a functional programming context.
Perform blazing fast equality comparisons (either deep or shallow) on two objects passed. It has no dependencies, and is ~1.27kB when minified and gzipped.
Unlike most equality validation libraries, the following types are handled out-of-the-box:
NaN
Date
objectsRegExp
objectsMap
/ Set
iterablesPromise
objectsreact
elementsStarting with version 1.5.0
, circular objects are supported for both deep and shallow equality (see circularDeepEqual
and circularShallowEqual
). You can also create a custom nested comparator, for specific scenarios (see below).
import { deepEqual } from 'fast-equals';
console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true
There are three builds, an ESM build for modern build systems / runtimes, a CommonJS build for traditional NodeJS environments, and a UMD build for legacy implementations. The ideal one will likely be chosen for you automatically, however if you want to use a specific build you can always import it directly:
fast-equals/dist/fast-equals.esm.js
nodejs
versions that do not allow ESM with file extensions other than .mjs
=> fast-equals/dist/fast-equals.mjs
fast-equals/dist/fast-equals.cjs.js
fast-equals/dist/fast-equals.js
fast-equals/dist/fast-equals.min.js
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
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:
type InternalEqualityComparator = (
a: any,
b: any,
indexOrKeyA: any,
indexOrKeyB: any,
parentA: any,
parentB: any,
meta: any,
) => boolean;
type TypeEqualityComparator = <Type, Meta>(
a: Type,
b: Type,
isEqual: InternalEqualityComparator,
meta: Meta,
) => boolean;
interface CreateComparatorCreatorOptions<Meta> {
areArraysEqual: TypeEqualityComparator<any[], Meta>;
areDatesEqual: TypeEqualityComparator<Date, Meta>;
areMapsEqual: TypeEqualityComparator<Map<any, any>, Meta>;
areObjectsEqual: TypeEqualityComparator<Record<string, any>, Meta>;
areRegExpsEqual: TypeEqualityComparator<RegExp, Meta>;
areSetsEqual: TypeEqualityComparator<Set<any>, Meta>;
createIsNestedEqual?: (
comparator: <A, B>(a: A, b: B, meta: Meta) => boolean,
) => InternalEqualityComparator;
}
function createCustomEqual(
getComparatorOptions: (
defaultOptions: CreateComparatorOptions,
) => Partial<CreateComparatorOptions>,
): EqualityComparator;
The meta
parameter above is whatever you want it to be. It will be passed through to all equality checks, and is meant specifically for use with custom equality methods. For example, with the circularDeepEqual
and circularShallowEqual
methods, it is used to pass through a cache of processed objects. You also only need to return the override handlers; absent being provided, the defaults are used.
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 comparisoncreateCustomCircularEqual
Operates nearly identically to createCustomEqual
, with the difference being that the meta
property expected by the comparator is expected to have a WeakMap
contract. This is because it is used for caching accessed objects to avoid maximum stack exceeded errors. The most common use for this method is a simple way to support circular checks for references that do not have WeakMap
natively.
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.
All benchmarks were performed on an i7-8650U Ubuntu Linux laptop with 24GB of memory using NodeJS version 12.19.1
, 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
elementsOperations / second | |
---|---|
fast-equals | 153,880 |
fast-deep-equal | 144,035 |
react-fast-compare | 130,324 |
nano-equal | 104,624 |
fast-equals (circular) | 97,610 |
shallow-equal-fuzzy | 83,946 |
underscore.isEqual | 47,370 |
lodash.isEqual | 25,053 |
deep-eql | 22,146 |
assert.deepStrictEqual | 532 |
deep-equal | 209 |
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 comparisonassert.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 watcherFAQs
A blazing fast equality comparison, either shallow or deep
The npm package fast-equals receives a total of 2,437,158 weekly downloads. As such, fast-equals popularity was classified as popular.
We found that fast-equals demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.