JavaScript Object Merging Challenge
You're building a system that requires combining configuration settings from multiple sources. Often, you'll have a default configuration and then a user-defined configuration that might override or add to those defaults. This challenge will test your ability to effectively merge two JavaScript objects.
Problem Description
Your task is to create a JavaScript function that takes two objects as input and returns a new object representing the merged result. The merging process should prioritize properties from the second object, meaning if a property exists in both objects, the value from the second object should be used in the merged output. The function should also handle nested objects by performing a deep merge.
Key Requirements:
- The function should accept exactly two arguments,
obj1andobj2, both of which are expected to be JavaScript objects. - The function must return a new object. The original objects should not be modified.
- If a property exists in both
obj1andobj2, the value fromobj2should take precedence. - If a property exists only in
obj1, it should be included in the merged object. - If a property exists only in
obj2, it should be included in the merged object. - The merge should be deep. If a property's value in both objects is itself an object, these nested objects should also be merged recursively. Non-object values will be overwritten as described above.
Expected Behavior:
- Primitive values (strings, numbers, booleans, null, undefined) from
obj2will overwrite those fromobj1. - Arrays are treated as primitive values and will be overwritten entirely by the array from
obj2if the property exists in both. - Nested objects will be merged recursively.
Edge Cases to Consider:
- What happens if one or both of the input objects are empty?
- What happens if the input arguments are not objects (e.g.,
null,undefined, primitives)? The function should handle these gracefully.
Examples
Example 1:
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { b: { d: 3 }, e: 4 };
Output:
{ a: 1, b: { c: 2, d: 3 }, e: 4 }
Explanation:
a: 1is fromobj1as it's not inobj2.bis an object in both. We recursively mergeobj1.bandobj2.b.obj2.bhasd: 3, so it's included.obj1.bhasc: 2, which is not inobj2.b, so it's also included.e: 4is fromobj2as it's not inobj1.
Example 2:
const obj1 = { name: "Default", settings: { theme: "light", fontSize: 12 } };
const obj2 = { settings: { fontSize: 14, language: "en" }, isActive: true };
Output:
{ name: "Default", settings: { theme: "light", fontSize: 14, language: "en" }, isActive: true }
Explanation:
name: "Default"is fromobj1.settingsis an object in both.obj2.settings.fontSize(14) overwritesobj1.settings.fontSize(12).obj1.settings.theme("light") is kept as it's not inobj2.settings.obj2.settings.language("en") is added.isActive: trueis fromobj2.
Example 3: Array Handling
const obj1 = { data: [1, 2], config: { items: [10, 20] } };
const obj2 = { data: [3, 4], config: { items: [30] } };
Output:
{ data: [3, 4], config: { items: [30] } }
Explanation:
datais an array in both. The array fromobj2([3, 4]) completely replaces the array fromobj1.configis an object in both. Theitemsproperty withinconfigis also an array. The array fromobj2([30]) replaces the array fromobj1.
Constraints
- The input objects can have any level of nesting.
- The function should be efficient for typical object sizes.
- Input arguments should ideally be objects, but the function should not throw errors if
nullorundefinedare passed; it should return an object based on the non-null/undefined input.
Notes
- Consider how to check if a value is an object and not
null. - Think about how to handle circular references if they were a concern (though for this challenge, you can assume no circular references).
- A good approach might involve recursion.