Lodash v4 deep cloning and merge behavior for inherited properties
0 reputation · 15 Oct 2022, 09:29 UTC
0 reputation · 15 Oct 2022, 09:29 UTC
The transition from Lodash v3 to v4 introduced significant changes in how the library handles object manipulation, specifically regarding the shift toward immutable patterns and the reduction of the API surface.
A critical design consideration involves the handling of inherited properties during deep operations. While v3 and v4 differ in their approach to cloning and merging, there is uncertainty regarding the precise boundary where inherited properties are excluded or included when processing nested objects via _.merge compared to _.cloneDeep.
Given the goal of maintaining strict control over object prototypes during deep copies, the following points require clarification:
_.cloneDeep preserve the prototype chain of the source object in v4?_.merge treat inherited properties from the source object when the target object already possesses a corresponding own property?29275 reputation · 15 Oct 2022, 17:31 UTC
1. Does _.cloneDeep preserve the prototype chain of the source object in v4? No. _.cloneDeep creates a new plain object and copies only the source’s own enumerable string‑keyed properties. Any properties that come from the source’s prototype are not copied, and the resulting clone has the default Object.prototype as its prototype.
2. How does _.merge treat inherited properties from the source object when the target already possesses a corresponding own property? _.merge iterates only over the source’s own enumerable string‑keyed properties. Inherited properties are ignored. If the target already has an own property with the same key, that property is overwritten by the source’s own value; inherited properties never influence the merge.
_.cloneDeep and _.merge operate solely on own enumerable string‑keyed properties.Object.prototype or a custom prototype) are not copied or merged._.merge overwrites it; inherited properties on the target are untouched.const _ = require('lodash');
// Prototype with an inherited property
const proto = { inherited: 42 };
const child = Object.create(proto);
child.own = 'value';
// cloneDeep example
const cloned = _.cloneDeep(child);
console.log(cloned); // { own: 'value' } – no inherited property
// merge example
const target = { a: 1, inherited: 99 }; // inherited is an own property here
_.merge(target, child);
console.log(target); // { a: 1, inherited: 99, own: 'value' }
// Notice the target’s own "inherited" stays unchanged; child’s inherited is ignored
If your application requires that the clone or merged object retain the original prototype (for instance, when working with class instances), the default lodash functions won’t suffice. In that case you can:
_.cloneDeepWith or _.mergeWith and provide a customizer that copies Object.getPrototypeOf onto the new object.Object.setPrototypeOf(cloned, Object.getPrototypeOf(child));Do you need the clone or merged result to keep the same prototype as the source, or is a plain object acceptable for your use case?
Use comments to ask for clarification. Post a solution as an answer.
29,275 reputation · 15 Oct 2022, 18:03 UTC
While _.cloneDeep and _.merge ignore inherited properties by default, lodash provides wrapper functions that let you intervene:
_.cloneDeepWith(value, customizer) – the customizer receives each value and can return a value to be used; returning undefined falls back to the default deep copy._.mergeWith(object, sources, customizer) – the customizer is invoked for each property pair; you can choose to copy inherited properties by returning the source value when the key is found on the source’s prototype.For example, to preserve a specific inherited property during a deep clone:
const _ = require('lodash');
const proto = { hidden: 'keep' };
const obj = Object.create(proto);
obj.own = 1;
const cloned = _.cloneDeepWith(obj, (value) => {
// If the value comes from the prototype and is the hidden property, keep it
if (value === proto.hidden && Object.getPrototypeOf(obj).hasOwnProperty('hidden')) {
return value;
}
return undefined; // let lodash handle everything else
});
console.log(cloned); // { own: 1, hidden: 'keep' }
Similarly, _.mergeWith can be used to merge inherited getters/setters when needed.