How to Use Lodash isEqualWith Custom Comparator
Lodash's _.isEqualWith method extends the library's
standard deep-comparison algorithm by accepting a custom comparator
function known as a customizer. This article provides a
focused guide on how _.isEqualWith handles complex object
evaluation, explains the parameters available within the customizer
callback, and demonstrates how to implement specialized equality rules
while delegating standard comparisons back to Lodash's internal
engine.
Understanding the Customizer Signature
The _.isEqualWith function takes three primary
arguments: the base value (value), the comparison target
(other), and the custom comparison callback
(customizer):
_.isEqualWith(value, other, [customizer]);When evaluating values, Lodash invokes the customizer
function with up to six arguments depending on the context of the
comparison:
objValue: The current property value or element from the source object.othValue: The corresponding property value or element from the target object.index|key: The property name (for objects) or index (for arrays) currently being evaluated.object: The parent object containingobjValue.other: The parent object containingothValue.stack: An internal Lodash structure used to track circular references and recursion state.
The Role of the
undefined Return Value
The most crucial aspect of evaluating complex objects with
_.isEqualWith is the return value of the
customizer.
- Explicit Booleans (
true/false): If the function returns a boolean value, Lodash treats the result as definitive for that specific property comparison. Returningtrueflags the values as identical, bypassing further recursive checks for that branch. Returningfalseimmediately fails the equality test for those values. - Returning
undefined: If thecustomizerreturnsundefined, Lodash falls back to its built-in deep-comparison algorithm (_.isEqual). This allows developers to intercept only the specific properties or types that require custom evaluation rules while leaving the rest of a complex, nested data structure to standard processing.
Practical Implementation: Complex Object Evaluation
Consider a scenario where two complex user entities must be compared. The comparison needs to ignore unique transaction IDs, perform case-insensitive string checks on email addresses, and rely on standard recursive comparison for all other deeply nested properties (such as profile details and permission lists).
const _ = require('lodash');
const userA = {
id: 'usr_101',
email: 'Developer@Example.com',
metadata: {
lastLogin: new Date('2023-01-01T10:00:00Z'),
roles: ['admin', 'editor']
}
};
const userB = {
id: 'usr_999', // Different ID
email: 'developer@example.com', // Different casing
metadata: {
lastLogin: new Date('2023-01-01T10:00:00Z'),
roles: ['admin', 'editor']
}
};
function userComparator(objValue, othValue, key) {
// Ignore specific property differences
if (key === 'id') {
return true;
}
// Custom evaluation for email strings (case-insensitive)
if (key === 'email' && typeof objValue === 'string' && typeof othValue === 'string') {
return objValue.toLowerCase() === othValue.toLowerCase();
}
// Return undefined to let Lodash handle nested objects, arrays, and dates
return undefined;
}
const areEqual = _.isEqualWith(userA, userB, userComparator);
console.log(areEqual); // Output: trueTraversing Deep Structures
When executing on nested objects, _.isEqualWith calls
the customizer at every step of traversal. The root objects are passed
first, followed by each descending key-value pair.
Because the customizer returns undefined at the root
object level, Lodash traverses downward into userA.metadata
and userB.metadata, invoking the customizer again for the
metadata object, followed by lastLogin and
roles. This mechanism gives you granular control over
evaluation logic at any depth without needing to rewrite recursive
tree-traversal logic manually.