Fast deep equality comparison that treats missing properties as equal to undefined
.
Standard deep equality functions treat these objects as different:
const obj1 = { a: 1 };
const obj2 = { a: 1, b: undefined };
But in many real-world scenarios (API responses, optional fields, configuration objects), you want these to be considered equal. That's what loose-deep-equal
does.
- π Fast - Optimized for performance with early exits and minimal overhead
- π― Loose equality - Missing properties are treated as
undefined
- π¦ Zero dependencies - Small and self-contained
- π§ Drop-in replacement - Compatible with other deep equality functions
- π ES6+ Support - Full support for Maps, Sets, TypedArrays, and BigInt
npm install loose-deep-equal
const looseEqual = require('loose-deep-equal');
looseEqual({ a: 1 }, { a: 1, b: undefined }); // true
// Default import
import looseEqual from 'loose-deep-equal';
// Named import
import { looseEqual } from 'loose-deep-equal';
looseEqual({ a: 1 }, { a: 1, b: undefined }); // true
// Missing properties are treated as undefined
looseEqual({ a: 1 }, { a: 1, b: undefined }); // true
// Regular deep equality still works
looseEqual({ a: { b: 2 } }, { a: { b: 2 } }); // true
// null is not undefined
looseEqual({ a: null }, { a: undefined }); // false
looseEqual({ a: null }, {}); // false
// Works with arrays
looseEqual([1, 2], [1, 2]); // true
// Works with nested structures
looseEqual(
{ user: { name: 'John' } },
{ user: { name: 'John', age: undefined } }
); // true
// ES6 types are fully supported
looseEqual(new Map([[1, 2]]), new Map([[1, 2]])); // true
looseEqual(new Set([1, 2, 3]), new Set([3, 2, 1])); // true
Scenario | loose-deep-equal |
fast-deep-equal |
lodash.isEqual |
---|---|---|---|
{a: 1} vs {a: 1, b: undefined} |
β
true |
β false |
β false |
{a: null} vs {a: undefined} |
β false |
β false |
β false |
Simple equal objects | 71.8% speed | 100% (baseline) | 13.4% speed |
Objects with undefined | 33.5% speed | 100% (baseline) | 19.0% speed |
Large objects (100+ props) | 96.5% speed | 100% (baseline) | 81.5% speed |
ES6 Maps/Sets support | β Full | β Full | β Full |
TypedArray support | β Full | β Full | β Full |
Use loose-deep-equal
when:
- Comparing API responses where fields might be omitted or explicitly set to
undefined
- Working with configuration objects with optional properties
- Implementing state management where undefined and missing are semantically equivalent
- Migrating between APIs that handle optional fields differently
Don't use loose-deep-equal
when:
- You need to distinguish between missing properties and
undefined
- You're working with data where
undefined
has special meaning - You need strict equality semantics
The algorithm:
- If objects have the same number of keys, uses standard fast comparison
- If different number of keys, checks all properties from both objects
- Missing properties are treated as
undefined
during comparison - Handles all standard JavaScript types including Date, RegExp, typed arrays, etc.
loose-deep-equal
is highly optimized for performance. Here are the benchmark results:
Scenario | Performance | Details |
---|---|---|
Simple equal objects | 71.8% | Objects with same structure |
Nested objects | 85.1% | Deep object hierarchies |
Large objects (100+ properties) | 96.5% | Nearly identical performance |
Objects with undefined properties | 33.5% | Our special case - checking all keys |
- Fast path when objects have same number of keys (70-96% of original speed)
- Efficient double-loop algorithm for different key counts
- Early exit on first difference
- No intermediate Set creation for key comparison
- Native support for ES6 types without performance penalty
- Still significantly faster than
lodash.isEqual
in all cases (3-6x faster) - Much faster than the
deep-equal
library (100-1000x faster) - Minimal overhead from Map/Set/TypedArray support
- β Objects with null prototype
- β
Objects with overridden
hasOwnProperty
- β
Sparse arrays (holes treated as
undefined
) - β Symbol properties (ignored, like other libraries)
- β Non-enumerable properties (ignored)
β οΈ Arrays with extra properties: Only numeric indices are compared, extra properties are ignored. This matchesfast-deep-equal
behavior.const arr1 = [1, 2, 3]; arr1.customProp = 'value1'; const arr2 = [1, 2, 3]; arr2.customProp = 'value2'; looseEqual(arr1, arr2); // true - extra properties ignored
Compares two values for loose deep equality.
Parameters:
a
(any) - First valueb
(any) - Second value
Returns: boolean
- True if values are loosely equal
Example:
const looseEqual = require('loose-deep-equal');
looseEqual({ x: 1 }, { x: 1, y: undefined }); // true
looseEqual([1, 2, 3], [1, 2, 3]); // true
looseEqual(null, undefined); // false
TypeScript definitions are included:
import looseEqual from 'loose-deep-equal';
const result: boolean = looseEqual({ a: 1 }, { a: 1, b: undefined });
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
Based on the excellent fast-deep-equal by @epoberezkin.