All Projects → planttheidea → Fast Equals

planttheidea / Fast Equals

Licence: mit
A blazing fast equality comparison, either shallow or deep

Programming Languages

typescript
32286 projects

fast-equals

Perform blazing fast equality comparisons (either deep or shallow) on two objects passed. It has no dependencies, and is ~1.3kB when minified and gzipped.

Unlike most equality validation libraries, the following types are handled out-of-the-box:

  • NaN
  • Date objects
  • RegExp objects
  • Map / Set iterables
  • Promise objects
  • react elements

Starting 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).

Table of contents

Usage

You can either import the individual functions desired:

import { deepEqual } from 'fast-equals';

console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true

Or if you want to import all functions under a namespace:

import * as fe from 'fast-equals';

console.log(fe.deep({ foo: 'bar' }, { foo: 'bar' })); // true

Specific builds

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:

  • ESM => fast-equals/dist/fast-equals.esm.js
    • For older nodejs versions that do not allow ESM with file extensions other than .mjs => fast-equals/dist/fast-equals.mjs
  • CommonJS => fast-equals/dist/fast-equals.cjs.js
  • UMD => fast-equals/dist/fast-equals.js

There is also a pre-minified version of the UMD build available:

  • Minified UMD => fast-equals/dist/fast-equals.min.js

Available methods

deepEqual

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

shallowEqual

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

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.

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

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.

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

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.

const array = ['foo'];

array.push(array);

console.log(circularShallowEqual(array, ['foo', array])); // true
console.log(circularShallowEqual(array, [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 partial-application function that will receive the internal comparator and should return a function that compares two objects.

The signature is as follows:

type EqualityComparator = (a: any, b: any, meta?: any) => boolean;
type EqualityComparatorCreator = (
  deepEqual: EqualityComparator,
) => EqualityComparator;

function createCustomEqual(
  createIsEqual?: EqualityComparatorCreator,
): EqualityComparator;

The meta parameter in EqualityComparator 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.

An example for a custom equality comparison that also checks against values in the meta object:

import { createCustomEqual } from 'fast-equals';

const isDeepEqualOrFooMatchesMeta = createCustomEqual(
  (deepEqual) => (objectA, objectB, meta) => (
    objectA.foo === meta ||
    objectB.foo === meta ||
    deepEqual(objectA, objectB, meta)
  )
);

const objectA = { foo: 'bar' };
const objectB = { foo: 'baz' };
const meta = 'bar';

console.log(isDeepEqualOrFooMatchesMeta(objectA, objectB, meta)); // true

Benchmarks

All benchmarks were performed on an i7 8-core Arch Linux laptop with 16GB of memory using NodeJS version 10.15.3, 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
Operations / second
fast-equals 142,730
nano-equal 115,530
shallow-equal-fuzzy 102,633
fast-deep-equal 102,335
react-fast-compare 100,036
fast-equals (circular) 79,589
underscore.isEqual 63,390
deep-equal 48,783
lodash.isEqual 24,456
deep-eql 24,196
assert.deepStrictEqual 1,382

Caveats that impact the benchmark (and accuracy of comparison):

  • assert.deepStrictEqual does not support NaN or SameValueZero equality for dates
  • deep-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 dates
  • fast-deep-equal does not support NaN or SameValueZero equality for dates
  • nano-equal does not strictly compare object property structure, array length, or object type, nor SameValueZero equality for dates
  • react-fast-compare does not support NaN or SameValueZero equality for dates, and does not compare function equality
  • shallow-equal-fuzzy does not strictly compare object type or regexp values, nor SameValueZero equality for dates
  • underscore.isEqual does not support SameValueZero 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. Maps, Promises, and Sets were excluded from the benchmark entirely because no library other than deep-eql fully supported their comparison. The same logic applies to react elements (which can be circular objects), but simple elements are non-circular objects so 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, and browser distributables with rollup
  • clean => run rimraf on the dist folder
  • dev => start webpack playground App
  • dist => run build
  • lint => run ESLint on all files in src folder (also runs on dev script)
  • lint:fix => run lint script, but with auto-fixer
  • prepublish:compile => run lint, test:coverage, transpile:lib, transpile:es, and dist 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 via nyc
  • test:watch => run same script as test but keep persistent watcher
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].