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)objValue: The current value of the property in the destination object.srcValue: The value of the property in the source object.key: The name of the property being assigned.object: The destination object.source: The source object providingsrcValue.
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.