JavaScript Deep Object Merge
You're tasked with creating a robust deepMerge function in JavaScript. This function will take two objects and merge them recursively, ensuring that nested objects are also merged, not overwritten. This is a common utility function in JavaScript development, particularly useful for managing configuration objects, state updates, or merging API responses.
Problem Description
Implement a JavaScript function deepMerge(target, source) that takes two objects, target and source, and returns a new object representing the deep merge of source into target.
The merge should behave as follows:
- Primitive values: If a key exists in both objects and its value in
sourceis not an object (or is null), the value fromsourceshould overwrite the value intarget. - Arrays: If a key exists in both objects and its value in both is an array, the arrays should be concatenated.
- Objects: If a key exists in both objects and its value in both is an object (and not null or an array), the function should recursively call
deepMergefor those nested objects. - New properties: Properties present only in
sourceshould be added to the merged object. - Properties only in target: Properties present only in
targetshould be retained. - Immutability: The original
targetandsourceobjects should not be modified. The function must return a new object.
Examples
Example 1:
const target = {
a: 1,
b: {
c: 2,
d: 3
},
e: [1, 2]
};
const source = {
b: {
c: 10,
f: 4
},
e: [3, 4],
g: 5
};
const result = deepMerge(target, source);
{
"a": 1,
"b": {
"c": 10,
"d": 3,
"f": 4
},
"e": [1, 2, 3, 4],
"g": 5
}
Explanation:
afromtargetis kept.bis a nested object. Its propertiesc(overwritten),d(kept from target), andf(added from source) are merged.eis an array. The arrays fromtargetandsourceare concatenated.gfromsourceis added.
Example 2:
const target = {
user: {
name: "Alice",
address: {
street: "123 Main St",
city: "Anytown"
}
},
settings: {
theme: "dark"
}
};
const source = {
user: {
age: 30,
address: {
city: "Othertown",
zip: "12345"
}
},
settings: null // null should overwrite object
};
const result = deepMerge(target, source);
{
"user": {
"name": "Alice",
"address": {
"street": "123 Main St",
"city": "Othertown",
"zip": "12345"
},
"age": 30
},
"settings": null
}
Explanation:
user.namefromtargetis kept.user.addressis a nested object, merged recursively.cityis overwritten,streetis kept, andzipis added.user.agefromsourceis added.settingsinsourceisnull, which overwrites the object intarget.
Example 3: (Edge case: null and undefined values)
const target = {
a: 1,
b: null,
c: undefined,
d: { nested: true }
};
const source = {
a: 5,
b: 2,
c: 3,
d: null
};
const result = deepMerge(target, source);
{
"a": 5,
"b": 2,
"c": 3,
"d": null
}
Explanation:
- Primitive values are overwritten as expected.
dis an object intargetandnullinsource.nulloverwrites the object.
Constraints
- The input objects will only contain JSON-serializable data types: primitives (strings, numbers, booleans, null, undefined), arrays, and plain JavaScript objects.
- The function should be reasonably performant for typical object sizes encountered in configuration or state management.
- The
targetandsourceobjects will always be valid JavaScript objects (notnullorundefinedas the top-level arguments).
Notes
- Consider how to distinguish between plain objects, arrays, and other types when deciding on the merge strategy.
- Be mindful of circular references, although for this challenge, you can assume inputs will not contain circular references.
- A good starting point is to create a copy of the
targetobject and then iterate through thesourceobject, applying the merge logic. - Helper functions to check if a value is a plain object or an array might be useful.