Ignore Timestamp Keys with Lodash isEqualWith

Deep equality checks in JavaScript frequently fail when comparing objects containing dynamic runtime data such as createdAt or updatedAt properties. The Lodash library solves this problem through _.isEqualWith, an extension of _.isEqual that accepts a customizer callback. This article explains how to write a customizer function that conditionally ignores specified timestamp keys, bypassing value checks on those dynamic fields while preserving standard deep-comparison logic for the remainder of the data structures.

The Customizer Function Signature

The _.isEqualWith method iterates through values and delegates comparison logic to the customizer whenever one is supplied:

_.isEqualWith(value, other, [customizer])

The customizer function receives several arguments on each step: customizer(objValue, othValue, key, object, other, stack)

Return Values

The customizer relies on specific return values to determine equality:

Implementing the Timestamp Bypass

To bypass specific timestamp keys, check if the current key matches your target dynamic properties. If it matches, return true. Otherwise, return undefined to let Lodash proceed with normal comparison.

const _ = require('lodash');

const IGNORED_TIMESTAMP_KEYS = new Set([
  'createdAt',
  'updatedAt',
  'timestamp',
  'lastModified'
]);

function ignoreTimestampsCustomizer(objValue, othValue, key) {
  if (IGNORED_TIMESTAMP_KEYS.has(key)) {
    return true;
  }
  return undefined;
}

Practical Example

Consider two objects representing database entities with identical business data but divergent timestamp values:

const recordA = {
  id: 101,
  user: {
    name: 'Jane Doe',
    role: 'Admin'
  },
  createdAt: '2023-01-15T08:30:00.000Z',
  updatedAt: '2023-01-15T08:30:00.000Z'
};

const recordB = {
  id: 101,
  user: {
    name: 'Jane Doe',
    role: 'Admin'
  },
  createdAt: '2023-01-15T08:30:00.000Z',
  updatedAt: '2023-08-20T14:45:12.000Z' // Divergent timestamp
};

// Standard comparison returns false
console.log(_.isEqual(recordA, recordB)); 
// Output: false

// Customizer comparison returns true
const isEqual = _.isEqualWith(recordA, recordB, ignoreTimestampsCustomizer);
console.log(isEqual); 
// Output: true

Adding Value Validation

If you want to ensure the ignored keys are actually valid timestamps rather than arbitrary data or missing properties, add a conditional format check inside the customizer:

function isIsoDateString(val) {
  return typeof val === 'string' && !isNaN(Date.parse(val));
}

function validatedTimestampCustomizer(objValue, othValue, key) {
  if (IGNORED_TIMESTAMP_KEYS.has(key)) {
    // Only ignore if both properties are valid date representations
    if (isIsoDateString(objValue) && isIsoDateString(othValue)) {
      return true;
    }
  }
  return undefined;
}

This ensures that if a key like updatedAt is corrupted (for example, containing null in one object and a valid string in another), the comparison will correctly report the mismatch.