Skip to content
LocalOnly
JSON Tools

How to Deep Merge JSON Objects in JavaScript and Python

The built-in ways to combine objects lose nested settings. Here is a short, tested deep merge for JavaScript and Python, and how to handle arrays, null and __proto__.

Updated October 18, 20263 min readTool runs in your browser

To deep merge JSON objects, you need a few lines of your own code. Merging a defaults object with user settings, or a base config with an environment override, is one of the most common things done with JSON in code. The built-in tools in both JavaScript and Python only merge the top level, and they lose nested values without any warning.

The JSON Merge tool merges documents in the browser, and its page explains the difference between shallow and deep merging. This guide shows how to do it in your own code, with a short function for each language.

Spread and dict union are shallow

const defaults = { db: { host: "localhost", port: 5432 }, tags: ["a"] };
const overrides = { db: { host: "db.internal" }, tags: ["b"] };

{ ...defaults, ...overrides }
// { db: { host: "db.internal" }, tags: ["b"] }   port is gone

The override's db object replaced the default db object entirely, so port disappeared. Python's dictionary union, defaults | overrides, and dict.update behave the same way. For flat settings that is fine. For nested config it silently drops values.

Deep merge JSON objects in JavaScript

const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);

function deepMerge(target, source) {
  const result = { ...target };
  for (const [key, value] of Object.entries(source)) {
    if (key === "__proto__") continue;
    result[key] =
      isPlainObject(result[key]) && isPlainObject(value) ? deepMerge(result[key], value) : value;
  }
  return result;
}

deepMerge(defaults, overrides);
// { db: { host: "db.internal", port: 5432 }, tags: ["b"] }

When both sides hold an object, it recurses. Otherwise the override wins. It returns a new object and leaves defaults unchanged, which matters when the same defaults are reused for every request.

Deep merge nested dicts in Python

def deep_merge(base, override):
    result = dict(base)
    for key, value in override.items():
        if isinstance(result.get(key), dict) and isinstance(value, dict):
            result[key] = deep_merge(result[key], value)
        else:
            result[key] = value
    return result

deep_merge(defaults, overrides)
# {'db': {'host': 'db.internal', 'port': 5432}, 'tags': ['b']}

Three decisions every deep merge makes

Arrays: replace, append or match

Both functions above replace arrays, so tags became ["b"]. That is the safest default, because nobody is surprised by it. Appending is right for lists that only grow, such as allowed origins. For lists of objects, merge items that share an ID:

function mergeById(base, override) {
  const byId = new Map(base.map((item) => [item.id, item]));
  for (const item of override) byId.set(item.id, deepMerge(byId.get(item.id) ?? {}, item));
  return [...byId.values()];
}

mergeById([{ id: 1, qty: 1, sku: "A" }, { id: 2, qty: 5 }], [{ id: 1, qty: 3 }, { id: 3, qty: 1 }]);
// [{ id: 1, qty: 3, sku: "A" }, { id: 2, qty: 5 }, { id: 3, qty: 1 }]

Watch out for libraries that merge arrays by position. lodash's merge combines ["a", "b", "c"] with ["z"] into ["z", "b", "c"], so an override can never shorten a list.

null: store it or delete the key

In the functions above, an override of null stores null. Some formats use null to mean remove this key instead, JSON Merge Patch for example. If you want that, add one line: when value === null, delete the key from the result and continue. Decide once and document it, because a caller cannot guess.

Unsafe keys

JSON.parse turns a key named __proto__ into an ordinary property, and a careless merge then writes it through to the object's prototype. The continue line in the JavaScript function skips it. Always keep that guard when merging JSON that came from outside your code, such as a request body or a user's settings file.

Frequently asked questions

Command Palette

Search for a tool or command