• Stars
    star
    471
  • Rank 93,216 (Top 2 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 7 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A blazing fast equality comparison, either shallow or deep

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 and Arguments)
  • Arrays
  • Typed Arrays
  • Date objects
  • RegExp objects
  • Map / Set iterables
  • Promise 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

import { deepEqual } from 'fast-equals';

console.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.

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

Comparing Maps

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:

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.

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

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.

const array = ['foo'];

array.push(array);

console.log(circularShallowEqual(array, ['foo', array])); // true
console.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
const 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;

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
const array = ['foo'];
const otherArray = ['foo'];

array.bar = 'baz';
otherArray.bar = 'baz';

console.log(strictDeepEqual(array, otherArray)); // true;
console.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
function 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

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
const 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

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:

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.

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.

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
Testing 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):

  • Maps, Promises, and Sets were excluded from the benchmark entirely because no library other than deep-eql fully supported their comparison
  • fast-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 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. 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, 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

More Repositories

1

fast-copy

A blazing fast deep object copier
JavaScript
1,133
star
2

moize

The consistently-fast, complete memoization solution for JS
TypeScript
892
star
3

unchanged

A tiny, fast, unopinionated handler for updating JS objects and arrays immutably
TypeScript
240
star
4

micro-memoize

A tiny, crazy fast memoization library for the 95% use-case
TypeScript
238
star
5

crio

Immutable objects and arrays in a natural way
JavaScript
211
star
6

hash-it

Hash any object type based on its values
TypeScript
206
star
7

remeasure

Get position and size attributes for any React Component
JavaScript
140
star
8

inline-loops.macro

Iteration helpers that inline to native loops for performance
JavaScript
100
star
9

selectorator

Simple generator of reselect selectors
TypeScript
96
star
10

react-style-tag

Write styles declaratively in React
TypeScript
68
star
11

react-pure-lifecycle

JavaScript
65
star
12

react-windowed-list

JavaScript
61
star
13

jile

Modular CSS in pure JavaScript
JavaScript
59
star
14

react-vidz-player

HTML5 videos in a React way
JavaScript
53
star
15

fast-stringify

A blazing fast stringifier that safely handles circular objects
TypeScript
51
star
16

react-billboardjs

React component for the billboard.js charting library
JavaScript
45
star
17

curriable

Curry any function with placeholder support
TypeScript
38
star
18

vidz

A zero-dependency, framework-agnostic video implementation
JavaScript
37
star
19

switchem

An extensible, functional switch with a chainable API
JavaScript
35
star
20

waddup

A ridiculously tiny pubsub manager with no dependencies
JavaScript
27
star
21

benchee

Simple benchmarks in both node and browser
TypeScript
27
star
22

react-local-redux

Manage component-specific state as you would global state via redux
JavaScript
23
star
23

arco

JavaScript
22
star
24

flexor

JavaScript
21
star
25

qonductor

Manage your data processing with sanity
JavaScript
19
star
26

react-parm

Handle react class instances with more functional purity
JavaScript
18
star
27

react-rendered-size

Get the rendered size of a React element without needing to render it
JavaScript
17
star
28

kari

JavaScript
14
star
29

pathington

JavaScript
13
star
30

get-object-class

A more explicit improvement on typeof
JavaScript
12
star
31

redux-browser-storage

Use redux to manage localStorage and sessionStorage data
JavaScript
11
star
32

remodeled

An abstraction for the React API with functional purity
JavaScript
11
star
33

convertify

Easily convert from one object class to the next
JavaScript
10
star
34

nage

Efficient, tiny object pool
TypeScript
9
star
35

tcf

A functional try / catch / finally with async support
JavaScript
8
star
36

bolster-css

JavaScript
7
star
37

printscout

Handle print events with ease
JavaScript
7
star
38

pure-object

JavaScript
7
star
39

react-jile

JavaScript
7
star
40

highcharts-config

Declarative Highcharts configuration generator with immutable, chainable API
JavaScript
6
star
41

react-idle-manager

JavaScript
6
star
42

react-redux-partitioner

Distribute state management for more performant reactivity
TypeScript
5
star
43

repoll

JavaScript
5
star
44

isit.js

Micro check library
JavaScript
4
star
45

identitate

Custom identity functions for composability
JavaScript
4
star
46

retip

A simple react tooltip
JavaScript
3
star
47

redux-slices

Manage slices of redux store in a concise, clear way
HTML
2
star
48

utilities

A collection of utilities used across projects
JavaScript
2
star
49

doozy

Transducer library for arrays, objects, sets, and maps
JavaScript
2
star
50

what-am-i

Simple validation library
TypeScript
2
star
51

promise-polyfill

Promise polyfill with custom opt-in error handling for debug
TypeScript
1
star
52

bolster

Library to augment jQuery with additional functionality
JavaScript
1
star
53

memzee

Function memoization based on only the most recent arguments
TypeScript
1
star
54

isifier

Make your own tiny, targeted validation library
JavaScript
1
star
55

diviso

Simple, flexible state management
1
star
56

singulum

State management with sanity
JavaScript
1
star
57

planttheidea.github.io

Github IO site for planttheidea
JavaScript
1
star