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:

The Role of the undefined Return Value

The most crucial aspect of evaluating complex objects with _.isEqualWith is the return value of the customizer.

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: true

Traversing 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.