{"id":13520423,"url":"https://github.com/planttheidea/fast-equals","last_synced_at":"2025-05-13T23:09:51.905Z","repository":{"id":25975284,"uuid":"107012895","full_name":"planttheidea/fast-equals","owner":"planttheidea","description":"A blazing fast equality comparison, either shallow or deep","archived":false,"fork":false,"pushed_at":"2025-04-29T02:14:00.000Z","size":2859,"stargazers_count":511,"open_issues_count":4,"forks_count":22,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-04T07:38:04.925Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/planttheidea.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-15T13:07:00.000Z","updated_at":"2025-05-01T19:52:08.000Z","dependencies_parsed_at":"2023-01-14T03:47:42.356Z","dependency_job_id":"98d8f8c7-0948-46b0-b177-ebb37b54d420","html_url":"https://github.com/planttheidea/fast-equals","commit_stats":{"total_commits":193,"total_committers":10,"mean_commits":19.3,"dds":"0.23834196891191706","last_synced_commit":"88d02a316d1f50c3a509ed774fa2dbc195d66438"},"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Ffast-equals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Ffast-equals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Ffast-equals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/planttheidea%2Ffast-equals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/planttheidea","download_url":"https://codeload.github.com/planttheidea/fast-equals/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254042306,"owners_count":22004895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T05:02:20.273Z","updated_at":"2025-05-13T23:09:46.889Z","avatar_url":"https://github.com/planttheidea.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Framework agnostic packages"],"sub_categories":["General utilities"],"readme":"# fast-equals\n\n\u003cimg src=\"https://img.shields.io/badge/build-passing-brightgreen.svg\"/\u003e\n\u003cimg src=\"https://img.shields.io/badge/coverage-100%25-brightgreen.svg\"/\u003e\n\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\"/\u003e\n\nPerform [blazing fast](#benchmarks) 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.\n\nThe following types are handled out-of-the-box:\n\n- Plain objects (including `react` elements and `Arguments`)\n- Arrays\n- Typed Arrays\n- `Date` objects\n- `RegExp` objects\n- `Map` / `Set` iterables\n- `Promise` objects\n- Primitive wrappers (`new Boolean()` / `new Number()` / `new String()`)\n- Custom class instances, including subclasses of native classes\n\nMethods 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.\n\n## Table of contents\n\n- [fast-equals](#fast-equals)\n  - [Table of contents](#table-of-contents)\n  - [Usage](#usage)\n    - [Specific builds](#specific-builds)\n  - [Available methods](#available-methods)\n    - [deepEqual](#deepequal)\n      - [Comparing `Map`s](#comparing-maps)\n    - [shallowEqual](#shallowequal)\n    - [sameValueZeroEqual](#samevaluezeroequal)\n    - [circularDeepEqual](#circulardeepequal)\n    - [circularShallowEqual](#circularshallowequal)\n    - [strictDeepEqual](#strictdeepequal)\n    - [strictShallowEqual](#strictshallowequal)\n    - [strictCircularDeepEqual](#strictcirculardeepequal)\n    - [strictCircularShallowEqual](#strictcircularshallowequal)\n    - [createCustomEqual](#createcustomequal)\n      - [Recipes](#recipes)\n  - [Benchmarks](#benchmarks)\n  - [Development](#development)\n\n## Usage\n\n```ts\nimport { deepEqual } from 'fast-equals';\n\nconsole.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true\n```\n\n### Specific builds\n\nBy 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:\n\n- ESM =\u003e `fast-equals/dist/esm/index.mjs`\n- CommonJS =\u003e `fast-equals/dist/cjs/index.cjs`\n- UMD =\u003e `fast-equals/dist/umd/index.js`\n- Minified UMD =\u003e `fast-equals/dist/min/index.js`\n\nIf you are having issues loading a specific build type, [please file an issue](https://github.com/planttheidea/fast-equals/issues).\n\n## Available methods\n\n### deepEqual\n\nPerforms a deep equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.\n\n```ts\nimport { deepEqual } from 'fast-equals';\n\nconst objectA = { foo: { bar: 'baz' } };\nconst objectB = { foo: { bar: 'baz' } };\n\nconsole.log(objectA === objectB); // false\nconsole.log(deepEqual(objectA, objectB)); // true\n```\n\n#### Comparing `Map`s\n\n`Map` objects support complex keys (objects, Arrays, etc.), however [the spec for key lookups in `Map` are based on `SameZeroValue`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#key_equality). If the spec were followed for comparison, the following would always be `false`:\n\n```ts\nconst mapA = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);\nconst mapB = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);\n\ndeepEqual(mapA, mapB);\n```\n\nTo 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`.\n\n### shallowEqual\n\nPerforms a shallow equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.\n\n```ts\nimport { shallowEqual } from 'fast-equals';\n\nconst nestedObject = { bar: 'baz' };\n\nconst objectA = { foo: nestedObject };\nconst objectB = { foo: nestedObject };\nconst objectC = { foo: { bar: 'baz' } };\n\nconsole.log(objectA === objectB); // false\nconsole.log(shallowEqual(objectA, objectB)); // true\nconsole.log(shallowEqual(objectA, objectC)); // false\n```\n\n### sameValueZeroEqual\n\nPerforms a [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-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`.\n\n```ts\nimport { sameValueZeroEqual } from 'fast-equals';\n\nconst mainObject = { foo: NaN, bar: 'baz' };\n\nconst objectA = 'baz';\nconst objectB = NaN;\nconst objectC = { foo: NaN, bar: 'baz' };\n\nconsole.log(sameValueZeroEqual(mainObject.bar, objectA)); // true\nconsole.log(sameValueZeroEqual(mainObject.foo, objectB)); // true\nconsole.log(sameValueZeroEqual(mainObject, objectC)); // false\n```\n\n### circularDeepEqual\n\nPerforms the same comparison as `deepEqual` but supports circular objects. It is slower than `deepEqual`, so only use if you know circular objects are present.\n\n```ts\nfunction Circular(value) {\n  this.me = {\n    deeply: {\n      nested: {\n        reference: this,\n      },\n    },\n    value,\n  };\n}\n\nconsole.log(circularDeepEqual(new Circular('foo'), new Circular('foo'))); // true\nconsole.log(circularDeepEqual(new Circular('foo'), new Circular('bar'))); // false\n```\n\nJust as with `deepEqual`, [both keys and values are compared for deep equality](#comparing-maps).\n\n### circularShallowEqual\n\nPerforms the same comparison as `shallowequal` but supports circular objects. It is slower than `shallowEqual`, so only use if you know circular objects are present.\n\n```ts\nconst array = ['foo'];\n\narray.push(array);\n\nconsole.log(circularShallowEqual(array, ['foo', array])); // true\nconsole.log(circularShallowEqual(array, [array])); // false\n```\n\n### strictDeepEqual\n\nPerforms the same comparison as `deepEqual` but performs a strict comparison of the objects. In this includes:\n\n- Checking symbol properties\n- Checking non-enumerable properties in object comparisons\n- Checking full descriptor of properties on the object to match\n- Checking non-index properties on arrays\n- Checking non-key properties on `Map` / `Set` objects\n\n```ts\nconst array = [{ foo: 'bar' }];\nconst otherArray = [{ foo: 'bar' }];\n\narray.bar = 'baz';\notherArray.bar = 'baz';\n\nconsole.log(strictDeepEqual(array, otherArray)); // true;\nconsole.log(strictDeepEqual(array, [{ foo: 'bar' }])); // false;\n```\n\n### strictShallowEqual\n\nPerforms the same comparison as `shallowEqual` but performs a strict comparison of the objects. In this includes:\n\n- Checking non-enumerable properties in object comparisons\n- Checking full descriptor of properties on the object to match\n- Checking non-index properties on arrays\n- Checking non-key properties on `Map` / `Set` objects\n\n```ts\nconst array = ['foo'];\nconst otherArray = ['foo'];\n\narray.bar = 'baz';\notherArray.bar = 'baz';\n\nconsole.log(strictDeepEqual(array, otherArray)); // true;\nconsole.log(strictDeepEqual(array, ['foo'])); // false;\n```\n\n### strictCircularDeepEqual\n\nPerforms the same comparison as `circularDeepEqual` but performs a strict comparison of the objects. In this includes:\n\n- Checking `Symbol` properties on the object\n- Checking non-enumerable properties in object comparisons\n- Checking full descriptor of properties on the object to match\n- Checking non-index properties on arrays\n- Checking non-key properties on `Map` / `Set` objects\n\n```ts\nfunction Circular(value) {\n  this.me = {\n    deeply: {\n      nested: {\n        reference: this,\n      },\n    },\n    value,\n  };\n}\n\nconst first = new Circular('foo');\n\nObject.defineProperty(first, 'bar', {\n  enumerable: false,\n  value: 'baz',\n});\n\nconst second = new Circular('foo');\n\nObject.defineProperty(second, 'bar', {\n  enumerable: false,\n  value: 'baz',\n});\n\nconsole.log(circularDeepEqual(first, second)); // true\nconsole.log(circularDeepEqual(first, new Circular('foo'))); // false\n```\n\n### strictCircularShallowEqual\n\nPerforms the same comparison as `circularShallowEqual` but performs a strict comparison of the objects. In this includes:\n\n- Checking non-enumerable properties in object comparisons\n- Checking full descriptor of properties on the object to match\n- Checking non-index properties on arrays\n- Checking non-key properties on `Map` / `Set` objects\n\n```ts\nconst array = ['foo'];\nconst otherArray = ['foo'];\n\narray.push(array);\notherArray.push(otherArray);\n\narray.bar = 'baz';\notherArray.bar = 'baz';\n\nconsole.log(circularShallowEqual(array, otherArray)); // true\nconsole.log(circularShallowEqual(array, ['foo', array])); // false\n```\n\n### createCustomEqual\n\nCreates 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.\n\nThe signature is as follows:\n\n```ts\ninterface Cache\u003cKey extends object, Value\u003e {\n  delete(key: Key): boolean;\n  get(key: Key): Value | undefined;\n  set(key: Key, value: any): any;\n}\n\ninterface ComparatorConfig\u003cMeta\u003e {\n  areArraysEqual: TypeEqualityComparator\u003cany[], Meta\u003e;\n  areDatesEqual: TypeEqualityComparator\u003cDate, Meta\u003e;\n  areErrorsEqual: TypeEqualityComparator\u003cError, Meta\u003e;\n  areFunctionsEqual: TypeEqualityComparator\u003c(...args: any[]) =\u003e any, Meta\u003e;\n  areMapsEqual: TypeEqualityComparator\u003cMap\u003cany, any\u003e, Meta\u003e;\n  areObjectsEqual: TypeEqualityComparator\u003cRecord\u003cstring, any\u003e, Meta\u003e;\n  arePrimitiveWrappersEqual: TypeEqualityComparator\u003c\n    boolean | string | number,\n    Meta\n  \u003e;\n  areRegExpsEqual: TypeEqualityComparator\u003cRegExp, Meta\u003e;\n  areSetsEqual: TypeEqualityComparator\u003cSet\u003cany\u003e, Meta\u003e;\n  areTypedArraysEqual: TypeEqualityComparatory\u003cTypedArray, Meta\u003e;\n  areUrlsEqual: TypeEqualityComparatory\u003cURL, Meta\u003e;\n}\n\nfunction createCustomEqual\u003cMeta\u003e(options: {\n  circular?: boolean;\n  createCustomConfig?: (\n    defaultConfig: ComparatorConfig\u003cMeta\u003e,\n  ) =\u003e Partial\u003cComparatorConfig\u003cMeta\u003e\u003e;\n  createInternalComparator?: (\n    compare: \u003cA, B\u003e(a: A, b: B, state: State\u003cMeta\u003e) =\u003e boolean,\n  ) =\u003e (\n    a: any,\n    b: any,\n    indexOrKeyA: any,\n    indexOrKeyB: any,\n    parentA: any,\n    parentB: any,\n    state: State\u003cMeta\u003e,\n  ) =\u003e boolean;\n  createState?: () =\u003e { cache?: Cache; meta?: Meta };\n  strict?: boolean;\n}): \u003cA, B\u003e(a: A, b: B) =\u003e boolean;\n```\n\nCreate 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](#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](https://github.com/planttheidea/fast-equals/issues).\n\n_**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._\n\n#### Recipes\n\nSome 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.\n\n- [Legacy environment support for `RegExp` comparators](./recipes/legacy-regexp-support.md)\n- [Explicit property check](./recipes/explicit-property-check.md)\n- [Using `meta` in comparison](./recipes//using-meta-in-comparison.md)\n- [Comparing non-standard properties](./recipes/non-standard-properties.md)\n- [Strict property descriptor comparison](./recipes/strict-property-descriptor-check.md)\n- [Legacy environment support for circualr equal comparators](./recipes/legacy-circular-equal-support.md)\n\n## Benchmarks\n\nAll benchmarks were performed on an i9-11900H Ubuntu Linux 24.04 laptop with 64GB of memory using NodeJS version `20.17.0`, and are based on averages of running comparisons based deep equality on the following object types:\n\n- Primitives (`String`, `Number`, `null`, `undefined`)\n- `Function`\n- `Object`\n- `Array`\n- `Date`\n- `RegExp`\n- `react` elements\n- A mixed object with a combination of all the above types\n\n```bash\nTesting mixed objects equal...\n┌─────────┬─────────────────────────────────┬────────────────┐\n│ (index) │ Package                         │ Ops/sec        │\n├─────────┼─────────────────────────────────┼────────────────┤\n│ 0       │ 'fast-equals'                   │ 1256867.529926 │\n│ 1       │ 'fast-deep-equal'               │ 1207041.997437 │\n│ 2       │ 'shallow-equal-fuzzy'           │ 1142536.391324 │\n│ 3       │ 'react-fast-compare'            │ 1140373.249605 │\n│ 4       │ 'dequal/lite'                   │ 708240.354044  │\n│ 5       │ 'dequal'                        │ 704655.931143  │\n│ 6       │ 'fast-equals (circular)'        │ 595853.718756  │\n│ 7       │ 'underscore.isEqual'            │ 433596.570863  │\n│ 8       │ 'assert.deepStrictEqual'        │ 310595.198662  │\n│ 9       │ 'lodash.isEqual'                │ 232192.454526  │\n│ 10      │ 'fast-equals (strict)'          │ 175941.250843  │\n│ 11      │ 'fast-equals (strict circular)' │ 154606.328398  │\n│ 12      │ 'deep-eql'                      │ 136052.484375  │\n│ 13      │ 'deep-equal'                    │ 854.061311     │\n└─────────┴─────────────────────────────────┴────────────────┘\n\nTesting mixed objects not equal...\n┌─────────┬─────────────────────────────────┬────────────────┐\n│ (index) │ Package                         │ Ops/sec        │\n├─────────┼─────────────────────────────────┼────────────────┤\n│ 0       │ 'fast-equals'                   │ 3795307.779634 │\n│ 1       │ 'fast-deep-equal'               │ 2987150.35694  │\n│ 2       │ 'react-fast-compare'            │ 2733075.404272 │\n│ 3       │ 'fast-equals (circular)'        │ 2311547.685659 │\n│ 4       │ 'dequal/lite'                   │ 1156909.54415  │\n│ 5       │ 'dequal'                        │ 1151209.161878 │\n│ 6       │ 'fast-equals (strict)'          │ 1102248.247412 │\n│ 7       │ 'fast-equals (strict circular)' │ 1020639.089577 │\n│ 8       │ 'nano-equal'                    │ 1009557.685012 │\n│ 9       │ 'underscore.isEqual'            │ 770286.698227  │\n│ 10      │ 'lodash.isEqual'                │ 296338.570457  │\n│ 11      │ 'deep-eql'                      │ 152741.182224  │\n│ 12      │ 'assert.deepStrictEqual'        │ 20163.203513   │\n│ 13      │ 'deep-equal'                    │ 3519.448516    │\n└─────────┴─────────────────────────────────┴────────────────┘\n```\n\nCaveats that impact the benchmark (and accuracy of comparison):\n\n- `Map`s, `Promise`s, and `Set`s were excluded from the benchmark entirely because no library other than `deep-eql` fully supported their comparison\n- `fast-deep-equal`, `react-fast-compare` and `nano-equal` throw on objects with `null` as prototype (`Object.create(null)`)\n- `assert.deepStrictEqual` does not support `NaN` or `SameValueZero` equality for dates\n- `deep-eql` does not support `SameValueZero` equality for zero equality (positive and negative zero are not equal)\n- `deep-equal` does not support `NaN` and does not strictly compare object type, or date / regexp values, nor uses `SameValueZero` equality for dates\n- `fast-deep-equal` does not support `NaN` or `SameValueZero` equality for dates\n- `nano-equal` does not strictly compare object property structure, array length, or object type, nor `SameValueZero` equality for dates\n- `react-fast-compare` does not support `NaN` or `SameValueZero` equality for dates, and does not compare `function` equality\n- `shallow-equal-fuzzy` does not strictly compare object type or regexp values, nor `SameValueZero` equality for dates\n- `underscore.isEqual` does not support `SameValueZero` equality for primitives or dates\n\nAll 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.\n\n## Development\n\nStandard practice, clone the repo and `npm i` to get the dependencies. The following npm scripts are available:\n\n- benchmark =\u003e run benchmark tests against other equality libraries\n- build =\u003e build `main`, `module`, and `browser` distributables with `rollup`\n- clean =\u003e run `rimraf` on the `dist` folder\n- dev =\u003e start webpack playground App\n- dist =\u003e run `build`\n- lint =\u003e run ESLint on all files in `src` folder (also runs on `dev` script)\n- lint:fix =\u003e run `lint` script, but with auto-fixer\n- prepublish:compile =\u003e run `lint`, `test:coverage`, `transpile:lib`, `transpile:es`, and `dist` scripts\n- start =\u003e run `dev`\n- test =\u003e run AVA with NODE_ENV=test on all files in `test` folder\n- test:coverage =\u003e run same script as `test` with code coverage calculation via `nyc`\n- test:watch =\u003e run same script as `test` but keep persistent watcher\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplanttheidea%2Ffast-equals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplanttheidea%2Ffast-equals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplanttheidea%2Ffast-equals/lists"}