Lodash assignWith: Prevent Overriding Properties

Lodash's _.assignWith function enables developers to customize how properties are merged from source objects into a target object. By supplying a customizer function as the final argument, you can intercept property assignments and implement logic to preserve existing values. This article explains how the customizer parameter works and demonstrates how to use it to prevent specific keys from being overwritten during assignment.

Understanding _.assignWith and the Customizer

The _.assignWith method behaves similarly to Object.assign or Lodash's _.assign, but it delegates assignment logic to a customizer callback. The signature of the customizer is:

function customizer(objValue, srcValue, key, object, source)

When the customizer returns undefined, _.assignWith falls back to its default behavior, assigning srcValue to the destination. If the customizer returns any other value, that returned value is used instead.

Protecting Specific Keys from Being Overwritten

To protect specific properties, check if the incoming key is one you want to preserve and whether the target object already contains a defined value for it. Returning objValue ensures the destination keeps its original value.

const _ = require('lodash');

const target = {
  id: 'USER_101',
  role: 'admin',
  status: 'active'
};

const source = {
  id: 'USER_999',
  role: 'guest',
  status: 'pending'
};

const protectedKeys = ['id', 'role'];

function preventOverride(objValue, srcValue, key) {
  // If the key is protected and already exists in the target, retain the target value
  if (protectedKeys.includes(key) && objValue !== undefined) {
    return objValue;
  }
  // Returning undefined tells Lodash to use default assignment
  return undefined;
}

const result = _.assignWith(target, source, preventOverride);

console.log(result);
// Output: { id: 'USER_101', role: 'admin', status: 'pending' }

In this example, id and role retain their original values from target, while status updates to 'pending' because the customizer returned undefined.

Preventing Overwrites for All Existing Properties

If the goal is to prevent any existing property on the destination object from being replaced, simplify the customizer to check if objValue is already defined:

const defaultsOnly = (objValue, srcValue) => {
  return objValue !== undefined ? objValue : srcValue;
};

const userSettings = { theme: 'dark' };
const incomingSettings = { theme: 'light', notifications: true };

const finalSettings = _.assignWith({}, userSettings, incomingSettings, defaultsOnly);

console.log(finalSettings);
// Output: { theme: 'dark', notifications: true }

By returning objValue whenever it is not undefined, existing attributes remain locked, allowing only newly introduced properties from source objects to be merged.