Merge JSON Objects in JavaScript

Merging JSON objects in JavaScript means combining two or more objects into one. The spread operator {...a, ...b} and Object.assign handle shallow merges in one line — later values overwrite earlier ones. For nested objects that should be combined rather than replaced, you need a deep merge.

Format and inspect JSON objects in Jsonic's formatter.

Open JSON Formatter

Shallow merge with the spread operator (simplest)

The spread operator copies all enumerable own properties from each source. Properties from the right operand overwrite those from the left. This is a shallow merge: nested objects are not recursively merged — they are replaced entirely.

const defaults = { color: "blue", size: "medium", visible: true }
const overrides = { color: "red", weight: 10 }

const merged = { ...defaults, ...overrides }
// { color: "red", size: "medium", visible: true, weight: 10 }

This pattern works for flat config objects, default value merging, and any situation where your objects do not have nested structure that needs combining.

Shallow merge with Object.assign

Object.assign copies own enumerable properties from one or more sources into a target object. When called with an existing object as the target, it mutates that object in place. Use an empty object as the target to get a non-destructive merge.

const target = { a: 1, b: 2 }
const source = { b: 3, c: 4 }

// Mutates target
Object.assign(target, source)
// target = { a: 1, b: 3, c: 4 }

// Non-destructive: use an empty object as target
const result = Object.assign({}, target, source)

Object.assign and the spread operator produce identical results for plain objects. The spread syntax is preferred in modern code for being more concise and readable.

The shallow merge problem with nested objects

When both objects have a property at the same key, the spread operator replaces the entire value — including nested objects. This is the most common source of subtle bugs when merging configuration or API payloads.

const user = {
  name: "Alice",
  prefs: { theme: "dark", lang: "en" }
}

const update = {
  prefs: { notifications: true }  // only wants to add notifications
}

const merged = { ...user, ...update }
// { name: "Alice", prefs: { notifications: true } }
// ❌ prefs.theme and prefs.lang are lost!

When you need to combine nested structures rather than replace them, use a deep merge function instead.

Deep merge with a recursive function

A recursive deep merge walks both objects simultaneously. When it encounters a key where both values are plain objects, it recurses into them. For any other value — including arrays, strings, and numbers — the source value wins.

function deepMerge(target, source) {
  const result = { ...target }
  for (const key of Object.keys(source)) {
    const srcVal = source[key]
    const tgtVal = result[key]
    if (
      srcVal &&
      typeof srcVal === 'object' &&
      !Array.isArray(srcVal) &&
      tgtVal &&
      typeof tgtVal === 'object' &&
      !Array.isArray(tgtVal)
    ) {
      result[key] = deepMerge(tgtVal, srcVal)
    } else {
      result[key] = srcVal
    }
  }
  return result
}

const merged = deepMerge(user, update)
// { name: "Alice", prefs: { theme: "dark", lang: "en", notifications: true } }

Arrays are treated as scalar values and replaced — not concatenated — by default. See the array merging section below for how to handle those separately.

Merge JSON from a string (parse first)

If your data arrives as a JSON string — from a file, an API response, orlocalStorage — parse it before merging. JSON.parse returns a plain JavaScript object that you can merge normally.

const jsonA = '{"name":"Alice","role":"user"}'
const jsonB = '{"role":"admin","active":true}'

const merged = {
  ...JSON.parse(jsonA),
  ...JSON.parse(jsonB),
}
// { name: "Alice", role: "admin", active: true }

// Back to JSON string
const output = JSON.stringify(merged, null, 2)

For more on the full parse-and-stringify workflow, see the JSON.parse JavaScript and JSON.stringify tutorial guides.

Merge arrays: concatenate vs deduplicate

When merging objects whose properties are arrays, decide whether you want all items (concatenation) or only unique items (deduplication with Set).

const a = { tags: ["json", "api"] }
const b = { tags: ["api", "rest"] }

// Concatenate all items
const concat = { tags: [...a.tags, ...b.tags] }
// { tags: ["json", "api", "api", "rest"] }

// Deduplicate with Set
const unique = { tags: [...new Set([...a.tags, ...b.tags])] }
// { tags: ["json", "api", "rest"] }

For arrays of objects (not primitives), you will need a custom deduplication strategy based on a unique identifier such as id.

Method comparison

MethodDepthMutates?ArraysUse when
{...a, ...b}ShallowNoReplacedSimple flat objects, ES2018+
Object.assign(, a, b)ShallowNo (empty target)ReplacedSame as spread, wider browser support
deepMerge(a, b) (recursive)DeepNoReplacedNested config objects
structuredClone + assignShallowNoReplacedNeed a true deep copy of target first

A note on structuredClone: it deep-clones the target so the original is never mutated, then Object.assign merges the source's top-level properties onto the clone.

// Deep copy a, then shallow-merge b's top-level properties
const result = Object.assign(structuredClone(a), b)

structuredClone is available in Node.js 17+, all modern browsers (2022+), and Deno. For older environments, use JSON.parse(JSON.stringify(a)) as a deep-clone fallback, keeping in mind it drops undefined, functions, and converts Date objects to strings.

Merging more than two objects

Chain the spread operator to merge three or more flat objects in a single expression. Properties from later objects overwrite earlier ones.

const base    = { color: "blue", size: "medium", border: "solid" }
const theme   = { color: "green", radius: "4px" }
const custom  = { color: "red", size: "large" }

const result = { ...base, ...theme, ...custom }
// { color: "red", size: "large", border: "solid", radius: "4px" }

For deep merges over an array of objects, reduce with your deep merge function:

const configs = [base, theme, custom]
const merged = configs.reduce((acc, obj) => deepMerge(acc, obj), {})

Frequently asked questions

What is the difference between a shallow merge and a deep merge?

A shallow merge copies only the top-level properties. If both objects have a nested object at the same key, the right-hand value replaces the left-hand value entirely. A deep merge recursively combines nested objects so properties at every level are preserved from both sources.

Does the spread operator mutate the original objects?

No. {...a, ...b} creates a new object. Neither a nor b is modified. Object.assign(target, source) does mutate target — use Object.assign(, a, b) with an empty object to avoid mutation.

How do I merge more than two JSON objects?

Chain the spread operator: {...a, ...b, ...c} or use Object.assign(, a, b, c). For deep merges, reduce over the array: [b, c].reduce((acc, obj) => deepMerge(acc, obj), a).

How are array properties handled when merging?

Both spread and Object.assign replace array properties — they do not concatenate arrays. If you want to concatenate, spread both arrays: { tags: [...a.tags, ...b.tags] }. Deduplicate with [...new Set([...a.tags, ...b.tags])].

Is structuredClone available in all environments?

structuredClone is available in Node.js 17+, all modern browsers (2022+), and Deno. For older environments, use JSON.parse(JSON.stringify(obj)) as a deep-clone fallback (but it drops undefined, functions, and Date objects become strings).

How do I merge nested JSON from an API response?

Parse both responses with JSON.parse, then deep-merge the plain objects. Example: deepMerge(JSON.parse(responseA), JSON.parse(responseB)). Alternatively, use a library like lodash.merge for production code that needs well-tested edge-case handling.

Format merged JSON

Paste your merged object into Jsonic's JSON Formatter to validate structure, pretty print nested keys, and catch any syntax issues before using the data in production.

Open JSON Formatter