Hone logo
Hone
Problems

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 source is not an object (or is null), the value from source should overwrite the value in target.
  • 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 deepMerge for those nested objects.
  • New properties: Properties present only in source should be added to the merged object.
  • Properties only in target: Properties present only in target should be retained.
  • Immutability: The original target and source objects 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:

  • a from target is kept.
  • b is a nested object. Its properties c (overwritten), d (kept from target), and f (added from source) are merged.
  • e is an array. The arrays from target and source are concatenated.
  • g from source is 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.name from target is kept.
  • user.address is a nested object, merged recursively. city is overwritten, street is kept, and zip is added.
  • user.age from source is added.
  • settings in source is null, which overwrites the object in target.

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.
  • d is an object in target and null in source. null overwrites 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 target and source objects will always be valid JavaScript objects (not null or undefined as 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 target object and then iterate through the source object, applying the merge logic.
  • Helper functions to check if a value is a plain object or an array might be useful.
Loading editor...
javascript