Skip to content

glideapps/loose-deep-equal

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

13 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

loose-deep-equal

Tests npm version

Fast deep equality comparison that treats missing properties as equal to undefined.

Why?

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.

Features

  • πŸš€ 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

Installation

npm install loose-deep-equal

Usage

CommonJS

const looseEqual = require('loose-deep-equal');

looseEqual({ a: 1 }, { a: 1, b: undefined }); // true

ES Modules

// Default import
import looseEqual from 'loose-deep-equal';

// Named import
import { looseEqual } from 'loose-deep-equal';

looseEqual({ a: 1 }, { a: 1, b: undefined }); // true

Examples

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

Comparison with other libraries

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

When to use this

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

When NOT to use this

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

How it works

The algorithm:

  1. If objects have the same number of keys, uses standard fast comparison
  2. If different number of keys, checks all properties from both objects
  3. Missing properties are treated as undefined during comparison
  4. Handles all standard JavaScript types including Date, RegExp, typed arrays, etc.

Performance

loose-deep-equal is highly optimized for performance. Here are the benchmark results:

Performance vs fast-deep-equal

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

Key optimizations:

  • 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

Benchmark details:

  • 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

Edge cases handled

  • βœ… 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 matches fast-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

API

looseEqual(a, b)

Compares two values for loose deep equality.

Parameters:

  • a (any) - First value
  • b (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

TypeScript definitions are included:

import looseEqual from 'loose-deep-equal';

const result: boolean = looseEqual({ a: 1 }, { a: 1, b: undefined });

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Credits

Based on the excellent fast-deep-equal by @epoberezkin.

About

JavaScript fast deep equality, but treating missing/undefined properties as equal

Resources

License

Stars

Watchers

Forks

Packages

No packages published