Lodash cloneDeepWith Customizer Guide

The _.cloneDeepWith method in Lodash provides a way to recursively clone JavaScript values while selectively customizing how specific properties or types are copied. By passing a customizer callback function, you can intercept the recursive cloning algorithm to preserve instances, omit sensitive fields, or handle unsupported types like DOM nodes and custom classes. This article details the signature of the customizer function, its arguments, return rules, and practical implementation patterns.

The Customizer Function Signature

The customizer function passed to _.cloneDeepWith(value, [customizer]) is invoked for each value traversed during the cloning process. It accepts up to four arguments:

function customizer(value, key, object, stack)

Return Values and Fallback Behavior

The behavior of _.cloneDeepWith depends strictly on what the customizer returns:

Common Implementation Patterns

1. Preserving Instances or Special Types

By default, _.cloneDeep converts custom class instances into plain objects. A customizer can retain references to specific instances or handle them using native cloning mechanisms:

const _ = require('lodash');

function preserveInstances(value) {
  // Preserve DOM nodes by reference or clone via native cloneNode
  if (_.isElement(value)) {
    return value.cloneNode(true);
  }

  // Preserve instances of a specific custom class
  if (value instanceof CustomHandler) {
    return new CustomHandler(value.settings);
  }

  // Fall back to default deep-cloning
  return undefined;
}

const cloned = _.cloneDeepWith(data, preserveInstances);

2. Modifying Values Based on Keys

The customizer can inspect the key argument to selectively mutate, mask, or omit specific fields:

const _ = require('lodash');

function maskSensitiveData(value, key) {
  if (key === 'password' || key === 'token') {
    return '[REDACTED]';
  }
  return undefined;
}

const user = {
  id: 101,
  profile: {
    username: 'jdoe',
    password: 'supersecretpassword'
  }
};

const sanitizedUser = _.cloneDeepWith(user, maskSensitiveData);

3. Handling Custom Data Types (e.g., RegEx or BigInt)

While modern versions of Lodash handle many built-in types, customizers can enforce specific copy logic for distinct primitives or data structures:

const _ = require('lodash');

function handleCustomTypes(value) {
  if (typeof value === 'bigint') {
    return BigInt(value.toString());
  }
  return undefined;
}

const payload = { id: 1n, nested: { count: 42n } };
const clonedPayload = _.cloneDeepWith(payload, handleCustomTypes);