https://github.com/glideapps/loose-deep-equal
JavaScript fast deep equality, but treating missing/undefined properties as equal
https://github.com/glideapps/loose-deep-equal
Last synced: 5 months ago
JSON representation
JavaScript fast deep equality, but treating missing/undefined properties as equal
- Host: GitHub
- URL: https://github.com/glideapps/loose-deep-equal
- Owner: glideapps
- License: mit
- Created: 2025-08-05T00:38:32.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2025-08-05T01:31:16.000Z (5 months ago)
- Last Synced: 2025-08-05T02:37:30.863Z (5 months ago)
- Language: JavaScript
- Size: 54.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# loose-deep-equal
[](https://github.com/glideapps/loose-deep-equal/actions/workflows/test.yml)
[](https://www.npmjs.com/package/loose-deep-equal)
Fast deep equality comparison that treats missing properties as equal to `undefined`.
## Why?
Standard deep equality functions treat these objects as different:
```javascript
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
```bash
npm install loose-deep-equal
```
## Usage
### CommonJS
```javascript
const looseEqual = require('loose-deep-equal');
looseEqual({ a: 1 }, { a: 1, b: undefined }); // true
```
### ES Modules
```javascript
// 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
```javascript
// 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.
```js
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:**
```javascript
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:
```typescript
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](https://github.com/epoberezkin/fast-deep-equal) by [@epoberezkin](https://github.com/epoberezkin).