Giter VIP home page Giter VIP logo

fast-deep-equal's Introduction

fast-deep-equal

The fastest deep equal with ES6 Map, Set and Typed arrays support.

Build Status npm Coverage Status

Install

npm install fast-deep-equal

Features

  • ES5 compatible
  • works in node.js (8+) and browsers (IE9+)
  • checks equality of Date and RegExp objects by value.

ES6 equal (require('fast-deep-equal/es6')) also supports:

  • Maps
  • Sets
  • Typed arrays

Usage

var equal = require('fast-deep-equal');
console.log(equal({foo: 'bar'}, {foo: 'bar'})); // true

To support ES6 Maps, Sets and Typed arrays equality use:

var equal = require('fast-deep-equal/es6');
console.log(equal(Int16Array([1, 2]), Int16Array([1, 2]))); // true

To use with React (avoiding the traversal of React elements' _owner property that contains circular references and is not needed when comparing the elements - borrowed from react-fast-compare):

var equal = require('fast-deep-equal/react');
var equal = require('fast-deep-equal/es6/react');

Performance benchmark

Node.js v12.6.0:

fast-deep-equal x 261,950 ops/sec ±0.52% (89 runs sampled)
fast-deep-equal/es6 x 212,991 ops/sec ±0.34% (92 runs sampled)
fast-equals x 230,957 ops/sec ±0.83% (85 runs sampled)
nano-equal x 187,995 ops/sec ±0.53% (88 runs sampled)
shallow-equal-fuzzy x 138,302 ops/sec ±0.49% (90 runs sampled)
underscore.isEqual x 74,423 ops/sec ±0.38% (89 runs sampled)
lodash.isEqual x 36,637 ops/sec ±0.72% (90 runs sampled)
deep-equal x 2,310 ops/sec ±0.37% (90 runs sampled)
deep-eql x 35,312 ops/sec ±0.67% (91 runs sampled)
ramda.equals x 12,054 ops/sec ±0.40% (91 runs sampled)
util.isDeepStrictEqual x 46,440 ops/sec ±0.43% (90 runs sampled)
assert.deepStrictEqual x 456 ops/sec ±0.71% (88 runs sampled)

The fastest is fast-deep-equal

To run benchmark (requires node.js 6+):

npm run benchmark

Please note: this benchmark runs against the available test cases. To choose the most performant library for your application, it is recommended to benchmark against your data and to NOT expect this benchmark to reflect the performance difference in your application.

Enterprise support

fast-deep-equal package is a part of Tidelift enterprise subscription - it provides a centralised commercial support to open-source software users, in addition to the support provided by software maintainers.

Security contact

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerability via GitHub issues.

License

MIT

fast-deep-equal's People

Contributors

chrisbolin avatar danalloway avatar dependabot-preview[bot] avatar epoberezkin avatar futurfrukt avatar kaievns avatar krzysiek1507 avatar langpavel avatar linusu avatar mokeev1995 avatar streamich avatar xobotyi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fast-deep-equal's Issues

Does not work with two identical function declarations

I got a class called LoadingModel and it is represented as:

export class LoadingModel<T> {

  static empty = <T>() => new LoadingModel<T>(false, false, false, undefined)

  static success<T>(success: T): LoadingModel<T> {
    return new LoadingModel(false, false, true, success)
  }

  static error<T>(error?: Error, optionalSuccess?: T): LoadingModel<T> {
    return new LoadingModel<T>(false, true, false, optionalSuccess, error)
  }

  loading = (success: T = this.success) => new LoadingModel(true, false, this.hasSuccess, success)

  public get success(): T {
    return this._success as T
  }

  public get optionalSuccess(): T | undefined {
    return this._success
  }

  constructor(
    public isLoading: boolean,
    private hasError: boolean,
    private hasSuccess: boolean,
    private _success?: T,
    public error?: Error) {
  }

  isEmpty = () => !this.isSuccess() && !this.isError()
  isSuccess = () => this.hasSuccess
  isError = () => this.hasError

  public toString = () => `Loading: ${this.isLoading}, Success: ${this.hasSuccess}:**${this._success}**, Error: ${this.hasError}:${this.error}`
}

I need to add:

else if (a && b && typeof a === 'function' && typeof b === 'function') {
    return a.toString() === b.toString()
  }

at the end of the equality check to see if both function bodies are the same, it is the same function. It seems as though different class instances with same function declared in class are incorrectly getting flagged for not equal, yet they are the same and the properties are all of the same.

Map object returns true regardless of key entries

Copied from node console:

> const equal = require('fast-deep-equal');
undefined
> const mapA = new Map();
undefined
> const mapB = new Map();
undefined
> equal(mapA, mapB)
true
> mapA.set('a', 1);
Map { 'a' => 1 }
> equal(mapA, mapB)
true

My guess is because they both have the same key properties

vite error: ReferenceError: module is not defined

Looks like there needs to be a esm "module" export needed.

2:21:41 AM [vite] ✨ dependencies updated, reloading page...
2:21:41 AM [vite] new dependencies found: @ctx-core/array, @ctx-core/event-log, fast-deep-equal/es6, updating...
2:21:42 AM [vite] Error when evaluating SSR module /@fs/user/project/node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal/es6/index.js:
ReferenceError: module is not defined
at /@fs/home/user/project/node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal/es6/index.js:9:1
at instantiateModule (/home/user/project/node_modules/.pnpm/[email protected]/node_modules/vite/dist/node/chunks/dep-11db14da.js:73323:166)

Error/testing notification

Just started using this fantastic utility.

After some initial tests it occurred to me that, where objects differ, it would be really useful to see precisely where they differ rather than having to manually scan the objects. This is obviously more relevant for large and deeper nested objects.

If this isn't currently possible it would be a great feature for testing when you get unexpected results. e.g. when you expect two objects to be equal but they aren't.

If performance is an issue then perhaps a setting can be enabled to switch this feature on for testing? Just an idea.

Set with different values but same length

Hi!

I've just tried your awesome work but I think something gone wrong;

const equal = require('fast-deep-equal');
// Returns true, expected false?
console.log(equal(new Set([1, 2, 3]), new Set([1, 2, 4]))); 

Seems to not work on Set

When using isDeepEqual with two Set with different order but same value it return false.

example :

isDeepEqual(new Set("1", "2"), new Set("2", "1")) => return false

add support Object.create(null)

Object.create(null).valueOf === undefined

if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();

error: a.valueOf is not a function

typed arrays

Typed arrays are handled like objects, by calling Object.keys. This crazy slow. They should be handled like arrays.

Comparing Proxies of RegExp throws

Apparently, RegExp#toString doesn't like being called on a proxy, and throws the following error:

TypeError: RegExp.prototype.source getter called on non-RegExp object

and that's what happen on this line:

if (regexpA && regexpB) return a.toString() == b.toString();
of the library when you try to compare proxies of regexps.

This is such an edge case that I'm not sure it's worth solving in this library, but I decided to share just in case anyone else encounters it.

use [email protected] in my project, but eslint依赖的fast-deep-equal 报错版本问题

npm ERR! code ELSPROBLEMS
npm ERR! invalid: [email protected]
[email protected] -> ./../../node_modules/.pnpm/[email protected]/node_modules/eslint
│ ├─┬ [email protected] -> ./../../node_modules/.pnpm/[email protected]/node_modules/ajv
│ │ └─┬ [email protected] -> ./../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal
│ │ └── [email protected] deduped invalid: "^16.12.0" from ../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal, "^16.12.0" from ../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal -> ./../../node_modules/.pnpm/[email protected]/node_modules/react
│ ├─┬ [email protected] -> ./../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal
│ │ └── [email protected] invalid: "^16.12.0" from ../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal, "^16.12.0" from ../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal -> ./../../node_modules/.pnpm/[email protected]/node_modules/react
│ └─┬ [email protected] -> ./../../node_modules/.pnpm/[email protected]/node_modules/table
│ └─┬ [email protected] -> ./../../node_modules/.pnpm/[email protected]/node_modules/ajv
│ └─┬ [email protected] -> ./../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal
│ └── [email protected] deduped invalid: "^16.12.0" from ../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal, "^16.12.0" from ../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal, "^16.12.0" from ../../node_modules/.pnpm/[email protected]/node_modules/fast-deep-equal -> ./../../node_modules/.pnpm/[email protected]/node_modules/react

Fix typings for es6 module with typescript

Error when loading the module in a typescript project:

Top-level declarations in .d.ts files must start with either a 'declare' or 'export' modifier.ts(1046)

es6/index.d.ts

const equal: (a: any, b: any) => boolean;
export = equal;

Should be

declare module 'fast-deep-equal/es6' {
  const equal: (a: any, b: any) => boolean;
  export = equal;
}

Bug: invalid dates are not equal

const deepEqual = require('fast-deep-equal')

deepEqual(
	new Date('foo'),
	new Date('bar'),
)	// false

I expect that invalid dates will be equal, the same as invalid numbers (NaN).

In lodash/underscore:

_.isEqual(new Date('foo'), new Date('bar')) // true

Throws error when comparing circular objects

Test code:

const equal = require('fast-deep-equal');

let a = {};
a.test = a;
let b = {};
b.test = b;

console.log(equal(a, b));
// Throws RangeError: Maximum call stack size exceeded

Any ideas for loop detecting before compare?

Feature request: ignore undefined values

in version 3.1.3 of the library the following comparison returns false

equal({ a: undefined }, {})

That's quite a surprise for me, but since there are test-cases for this case, I guess it works as expected.

It would be good to have an option to ignore undefined values.

ES6 in ES6

I know it might be annoying to have two sets of "same code", but modern JavaScript engines optimize ES6 code which means it is possible to reduce uglified JS size by about 400 bytes without sacrificing any performance (except on legacy JS engines).

So here is a version based on ES6 from beta, but with optimizations that reduce code size. On my machine this seems to give more ops/sec than the current beta ES6 version, although the difference is pretty small. Also I guess the benchmark does not yet include anything to test perf of ES6 features.

Optimizations

  1. BigInt arrays have one less boolean check on each iteration.
  2. constructor is stored in local variable and all calls using it are immediately after.
  3. Arrays and Typed arrays use the same path (with different comparison).
  4. Maps and Sets use the same path (Map has one extra comparison).
  5. Using .every() extensively to reduce code size.

Optimizations that use combined path will of course have slightly worse performance than if they were written separate (due to extra boolean storage & check per path). However the reduction in ops/sec is so tiny that I'd prefer shorter code due to slightly faster download and initial parsing on browser.

const typed = [
  Int8Array,
  Uint8Array,
  Uint8ClampedArray,
  Int16Array,
  Uint16Array,
  Int32Array,
  Uint32Array,
  Float32Array,
  Float64Array
];

if (typeof BigInt64Array !== 'undefined')
  typed.push(BigInt64Array, BigUint64Array);

module.exports = function equal(a, b) {
  if (a === b)
    return true;

  if (!a || !b || typeof a !== 'object' || typeof b !== 'object')
    // true if both NaN, false otherwise
    return a !== a && b !== b;

  const constructor = a.constructor;

  if (constructor !== b.constructor)
    return false;

  if (constructor === RegExp)
    return a.source === b.source && a.flags === b.flags;

  const isArray = Array.isArray(a);
  // .some(...) -> .includes(constructor) would be shorted, but not sure if that works correctly
  if (isArray || (constructor.BYTES_PER_ELEMENT && typed.some(type => a instanceof type))) {
    let i = a.length;

    if (i === b.length) {
      if (isArray)
        while (i-- > 0 && equal(a[i], b[i]));
      else
        while (i-- > 0 && a[i] === b[i]);
    }

    return i === -1;
  }

  const isMap = a instanceof Map;

  if (isMap || a instanceof Set) {
    if (a.size !== b.size)
      return false;

    if (!a.keys().every(b.has))
      return false;

    if (isMap && !a.keys().every(key => equal(a.get(key), b.get(key))))
      return false;

    return true;
  }

  if (a.valueOf !== Object.prototype.valueOf)
    return a.valueOf() === b.valueOf();

  if (a.toString !== Object.prototype.toString)
    return a.toString() === b.toString();

  const keys = Object.keys(a);

  if (keys.length !== Object.keys(b).length)
    return false;

  if (!keys.every(Object.prototype.hasOwnProperty.bind(b)))
    return false;

  return keys.every(key => equal(a[key], b[key]));
}

Uglified size: 1075 bytes.
Current ES6 beta uglified: 1473 bytes.

Edit: Although gzipped size difference is about ten bytes, so actual download size is about the same thus making the point of smaller download size near invalid :)

Break cycles

Comparing two object graphs with cycles sends this library into recursive tail-spin.

Having tested for this issue with both deep-equal, fast-deep-equal and deep-eql, I found that the only library where this works, is deep-eql, so you might consider referencing their approach.

If you don't wish to support object graphs with cycles, perhaps this should be explicitly omitted and noted in the documentation, since it creates pretty serious issues in test-frameworks etc.?

Not able to compare a Map

console.log(equal(Int16Array([1, 2]), Int16Array([1, 3]))) fails with "TypeError: Constructor Int16Array requires 'new'"
console.log(equal(new Int16Array([1, 2]), new Int16Array([1, 3]))) outputs true

Am I doing something silly?

Maximum call stack size exceeded for cyclic objects

The code below throws the RangeError:

var fastDeepEqual = require("fast-deep-equal")

const a = {aaa: 123};
a.a = a;

const b = {aaa: 123};
b.a = b;

fastDeepEqual(a,b)

Was loop detection omitted intentionally? If so it would be great to have paragraph on this in the docs.

for ... of loop break in IE11 with polyfills

Hi, I'm using the es6 version because I needs the Map and Set capabilities on the deep equal.
I'm locally transpiling my code to es6 with Map and Set polyfilled via corejs, but the problem is that if I use the /es6 version I automatically use the for...of loops that can't be used in IE11.
Is there any chance to avoid the for of loops?

Add support for WeakMap, WeakSet and Promise

As an example the following returns true, instead it should return false:

require ( 'fast-deep-equal/es6' )( new WeakMap (), new WeakMap () )

I think this types of objects should be checked for strict equality.

TypeScript declaration incorrect

When using this lib I get the following TS error: "error TS2309: An export assignment cannot be used in a module with other exported elements."

Ignore null fields options

I would love to see options object passed in with a ignoreIfNull attribute. I am constantly comparing objects which sometimes have their field initialised with null, but I only want to deep equals on the fields that have values.

toString comparison breaks if object has toString that isn't a function

If there is data passed into equals that has toString as a field that's not a function, equals breaks because it tries to call it as a function.

Example data:
{ field: "status", fieldtype: "jira", fieldId: "status", from: "10000", fromString: "To Do", to: "3", toString: "In Progress" }

I know that it's not ideal to have a field named toString, but this is external data that I don't have any control over.

NaN

Should equal(NaN, NaN) return true?

Question

Maybe I have misunderstood something , but why this line should work in general case?

if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();

What if I have my own toString and it, for example, always returns the same constant, no matter what the object is?

At least the following typescript code prints "true"

class A {
    n:number;
    constructor(n:number) {
        this.n = n;
    }
    toString():string  {
        return "a";
    }

}

const a1 = new A(1);
const a2 = new A(2);

if (a1.toString !== Object.prototype.toString) {
    const ret = a1.toString() === a2.toString();
    console.log(ret);
}

source code question

  1. What's the meaning of following source code?
{{? it.es6 }}
...
{{?}}
  1. Why not use Object.is to check NaN
// return a!==a && b!==b;
return Object.is(a, b)

False positive when a array item is modified using a non-numeric index/key

node: 8.9.0
npm: 5.5.1

The scenario below produces a false positive.

const fe = require('fast-deep-equal');
let oa = { 'a': 1, 'b': [1, { 'c': 'd' }], e: { 'a': [1, 2, '2'] } };
let ob = { 'a': 1, 'b': [1, { 'c': 'd' }], e: { 'a': [1, 2, '2'] } };

console.log(fe(oa,ob) ? 'equal' : 'not equal'); // equal
ob.b.c=1 // this is valid js although b is an array
console.log(fe(oa,ob) ? 'equal' :  'not equal') // equal <- incorrect

Testing deep equality gist has more tests

Support for comparing functions

We are using react-fast-compare and I need to compare functions in react when they are used as props. I have created PR to the react-fast-compare repository. They referenced me to try to get the change implemented here.

Are you open for PR implementing function comparison?

Original commit.
marcelmokos/react-fast-compare@887600a

Objects containing functions

Hi,

I was comparing two objects containing functions, even though the functions are equals in both objects, false is returned.

Is it the desired behaviour ?

`fast-deep-equal/react` not found

I get a MODULE_NOT_FOUND error when importing it as per README. I see no react folder in node_modules. Maybe it was not published?

array/object comparison is not symmetric

Due to the way Arrays are identified, it is possible to construct an object which "looks like" an array, but only when passed as the second argument:

const v1 = ['a', 'b'];
const v2 = { 0: 'a', 1: 'b', 2: 'c', length: 2, constructor: Array };
equal(v1, v2); // = true
equal(v2, v1); // = false

It doesn't seem particularly problematic or risky either way, but the asymmetry of the arguments is surprising.

There is also a similar (related?) issue that any extra properties on an array are not checked:

const v1 = Object.assign(['a', 'b'], { woo: 'hi' });
const v2 = ['a', 'b'];
equal(v1, v2); // = true
equal(v2, v1); // = true

Both of these behaviours diverge from the behaviour of util.isDeepStrictEqual

Support es6 object classes

  • typed arrays #29
  • Maps #26
  • Sets

What else?

Given that the main use case is comparing the objects that are close to JSON, supporting these classes in the main function would not add value for the most users and would substantially affect performance.

So the idea is to add an additional function that uses the current one and extends it to support these and other classes.

E.g.

const equal = require('fast-deep-equal').es6
// or
const equal = require('fast-deep-equal').extended
// or...

Any suggestions?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.