如何优化RxJS distinct操作符对嵌套完整对象的重复判断
Nice question! Stringifying objects to handle distinct checks works, but there are definitely cleaner and more efficient approaches for deep equality checks in RxJS—especially since you need to account for nested content and avoid false positives from matching IDs alone. Here are your best options:
1. Use distinctUntilChanged with a Deep Equality Comparator
If you only need to filter out consecutive duplicates (the most common use case), distinctUntilChanged accepts a custom comparator function. Lodash's isEqual is a battle-tested, optimized solution for deep equality checks—it’ll short-circuit as soon as it finds a mismatch, which is faster than stringifying the entire object.
import { distinctUntilChanged } from 'rxjs/operators'; import isEqual from 'lodash/isEqual'; // Your Observable emitting JSON objects const jsonObject$ = ...; // Filter consecutive deep-duplicate objects const distinctJsonObject$ = jsonObject$.pipe( distinctUntilChanged(isEqual) );
Why this is better than stringifying:
- Faster: Stops comparing as soon as a mismatch is found, instead of serializing the entire object.
- More reliable: Handles edge cases JSON.stringify misses (like properties in different order,
undefined, or non-JSON values likeSymbol).
2. Implement a Custom Deep Equality Function (No Dependencies)
If you don’t want to add Lodash to your project, you can write a simple recursive deep equality checker. This works great for basic JSON structures and avoids external dependencies.
import { distinctUntilChanged } from 'rxjs/operators'; function deepEqual(a: any, b: any): boolean { // Strict equality check for primitives/refs if (a === b) return true; // Handle null/undefined cases if (a == null || b == null) return false; // Ensure both values are the same type if (typeof a !== typeof b) return false; // Compare arrays if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (!deepEqual(a[i], b[i])) return false; } return true; } // Compare objects if (typeof a === 'object' && typeof b === 'object') { const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; for (const key of keysA) { if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false; } return true; } // Fallback to strict equality for remaining cases return a === b; } // Use with your Observable const distinctJsonObject$ = jsonObject$.pipe( distinctUntilChanged(deepEqual) );
3. Use distinct with a Content-Based Hash (For Full History Checks)
If you need to filter duplicates against all previous values (not just the immediate last one), distinct requires a key that uniquely identifies the object’s content. Instead of JSON.stringify, use a library like object-hash to generate a hash based on the object’s deep content—this ignores property order and handles nested structures reliably.
import { distinct } from 'rxjs/operators'; import hash from 'object-hash'; const jsonObject$ = ...; // Filter all deep-duplicate objects across the Observable's history const distinctJsonObject$ = jsonObject$.pipe( distinct(obj => hash(obj)) );
Why this beats JSON.stringify:
- Order-agnostic: Two objects with the same properties in different order will generate the same hash (JSON.stringify would treat them as different).
- Consistent: Handles non-JSON values (like
Dateobjects) correctly, whereas JSON.stringify converts dates to strings.
Key Takeaways
- Use
distinctUntilChanged + isEqual(or custom deepEqual) for consecutive duplicates—this is the most efficient option. - Use
distinct + object-hashif you need to check against all past values. - Avoid JSON.stringify for deep equality: it’s slow for large objects, sensitive to property order, and fails with non-JSON data types.
内容的提问来源于stack exchange,提问作者Manzur Khan

