How Lodash _.merge Prioritizes Nested Arrays
Lodash’s _.merge method recursively combines properties
from source objects into a destination object, but its handling of
nested arrays often surprises developers. Unlike shallow copy operations
or other deep-merging tools that overwrite or concatenate lists,
_.merge treats arrays as index-keyed objects. This article
breaks down how _.merge processes explicit nested arrays,
demonstrating its index-by-index priority mechanism and how it differs
from traditional object property resolution.
The Index-Based Merging Strategy
When _.merge encounters an array at a matching key in
both the target and source objects, it does not replace the target
array, nor does it append the new elements. Instead, it iterates through
the indices of the source array and merges each element into the
corresponding index of the target array.
Because arrays in JavaScript are internally objects with numerical
keys, _.merge treats an array like
{ '0': val1, '1': val2 }.
const target = { items: [1, 2, 3] };
const source = { items: [9, 8] };
_.merge(target, source);
// Result: { items: [9, 8, 3] }In this example:
- Index
0of the target (1) is overwritten by index0of the source (9). - Index
1of the target (2) is overwritten by index1of the source (8). - Index
2of the target (3) remains untouched because the source does not supply an element at index2.
Nested Objects Inside Arrays
The recursive nature of _.merge becomes most apparent
when nested arrays contain objects. Rather than replacing the object at
a given index, Lodash deeply merges the source object into the target
object residing at that same position.
const target = {
users: [
{ id: 1, name: "Alice", role: "User" },
{ id: 2, name: "Bob" }
]
};
const source = {
users: [
{ role: "Admin" },
{ name: "Robert", role: "Moderator" },
{ id: 3, name: "Charlie" }
]
};
_.merge(target, source);The resulting target.users evaluates to:
[
{ id: 1, name: "Alice", role: "Admin" },
{ id: 2, name: "Robert", role: "Moderator" },
{ id: 3, name: "Charlie" }
]At index 0, role is updated while
id and name are preserved. At index
1, name is overwritten and role
is added. At index 2, a new element is appended because the
target did not have an existing entry at that index.
Type Mismatches: Array vs. Non-Array
When a key conflicts across source and destination with mismatched
data types, _.merge prioritizes the source value:
- Source is an Array, Target is a Primitive/Object: The target value is converted or replaced by the source array (recursively merged if the target was a plain object, though numeric keys will align).
- Source is a Primitive/Object, Target is an Array: The source value completely overwrites the target array.
const target = { data: [1, 2, 3] };
const source = { data: "reset" };
_.merge(target, source);
// Result: { data: "reset" }Customizing Array
Merging with _.mergeWith
Because index-based mutation is often not the desired behavior for
arrays—concatenation or complete replacement is frequently
preferred—Lodash provides _.mergeWith. This method accepts
a customizer function to bypass the default array-merging logic:
function customizer(objValue, srcValue) {
if (_.isArray(objValue)) {
return objValue.concat(srcValue); // Concatenate instead of merging by index
}
}
const target = { tags: ["javascript"] };
const source = { tags: ["react"] };
_.mergeWith(target, source, customizer);
// Result: { tags: ["javascript", "react"] }Returning undefined from the customizer falls back to
the default _.merge behavior, allowing fine-grained control
over specific keys while keeping index-based merging intact for
others.