642

Both Object.assign and Object spread only do a shallow merge.

An example of the problem:

// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }

The output is what you'd expect. However if I try this:

// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }

Instead of

{ a: { a: 1, b: 1 } }

you get

{ a: { b: 1 } }

x is completely overwritten because the spread syntax only goes one level deep. This is the same with Object.assign().

Is there a way to do this?

8
  • is deep merging same as copying properties from one object to another?
    – user2879704
    Commented Jan 14, 2015 at 6:10
  • 2
    No, as object properties should not be overwritten, rather each child object should be merged into the same child on the target if it already exists.
    – Mike
    Commented Jan 14, 2015 at 6:17
  • 1
    @Oriol requires jQuery though...
    – m0meni
    Commented Jan 19, 2016 at 18:39
  • 3
    const merge = (p, c) => Object.keys(p).forEach(k => !!p[k] && p[k].constructor === Object ? merge(p[k], c[k]) : c[k] = p[k])
    – Xaqron
    Commented Aug 23, 2019 at 11:44
  • 2
    you could look on the following GitHub link to get the solution with short lines of codes https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6 Commented Jan 18, 2020 at 14:55

51 Answers 51

305

I know this is a bit of an old issue but the easiest solution in ES2015/ES6 I could come up with was actually quite simple, using Object.assign(),

Hopefully this helps:

/**
 * Simple object check.
 * @param item
 * @returns {boolean}
 */
export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

/**
 * Deep merge two objects.
 * @param target
 * @param ...sources
 */
export function mergeDeep(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();

  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (isObject(source[key])) {
        if (!target[key]) Object.assign(target, { [key]: {} });
        mergeDeep(target[key], source[key]);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }

  return mergeDeep(target, ...sources);
}

Example usage:

mergeDeep(this, { a: { b: { c: 123 } } });
// or
const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});  
console.dir(merged); // { a: 1, b: { c: { d: [Object] } } }

You'll find an immutable version of this in the answer below.

Note that this will lead to infinite recursion on circular references. There's some great answers on here on how to detect circular references if you think you'd face this issue.

15
  • 2
    if your object graph contains cycles that will lead to infinite recursion
    – the8472
    Commented Jan 20, 2016 at 3:05
  • 14
    Why write this: Object.assign(target, { [key]: {} }) if it could simply be target[key] = {}? Commented Oct 25, 2017 at 12:26
  • 7
    ...and target[key] = source[key] instead of Object.assign(target, { [key]: source[key] }); Commented Oct 25, 2017 at 12:27
  • 12
    This does not support any non-plain objects in target. For instance, mergeDeep({a: 3}, {a: {b: 4}}) will result in an augmented Number object, which is clearly not desired. Also, isObject does not accept arrays, but accepts any other native object type, such as Date, which should not be deep copied.
    – riv
    Commented Dec 13, 2017 at 12:46
  • 3
    This example seems to be from here blog.devgenius.io/… which contains a full explanation of code.
    – macasas
    Commented May 11, 2022 at 8:42
246

You can use Lodash merge:

var object = {
  'a': [{ 'b': 2 }, { 'd': 4 }]
};

var other = {
  'a': [{ 'c': 3 }, { 'e': 5 }]
};

console.log(_.merge(object, other));
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

9
  • 11
    Hey people, this is the simplest and most beautiful solution. Lodash is awesome, they should include it as core js object Commented Feb 2, 2018 at 8:32
  • 27
    Shouldn't the result be { 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }?
    – J. Hesters
    Commented Aug 16, 2018 at 11:34
  • 21
    The result { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } is correct, because we're merging elements of an array. The element 0 of object.a is {b: 2}, the element 0 of other.a is {c: 3}. When these two are merged because they have the same array index, the result is { 'b': 2, 'c': 3 }, which is the element 0 in the new object. Commented Sep 25, 2018 at 9:12
  • 19
    adding dependencies is not beautiful
    – stackers
    Commented May 21, 2021 at 20:53
  • 9
    @stackers reinventing the wheel is not beautiful Commented Dec 9, 2023 at 2:13
129
+50

The problem is non-trivial when it comes to host objects or any kind of object that's more complex than a bag of values

  • do you invoke a getter to obtain a value or do you copy over the property descriptor?
  • what if the merge target has a setter (either own property or in its prototype chain)? Do you consider the value as already-present or call the setter to update the current value?
  • do you invoke own-property functions or copy them over? What if they're bound functions or arrow functions depending on something in their scope chain at the time they were defined?
  • what if it's something like a DOM node? You certainly don't want to treat it as simple object and just deep-merge all its properties over into
  • how to deal with "simple" structures like arrays or maps or sets? Consider them already-present or merge them too?
  • how to deal with non-enumerable own properties?
  • what about new subtrees? Simply assign by reference or deep clone?
  • how to deal with frozen/sealed/non-extensible objects?

Another thing to keep in mind: Object graphs that contain cycles. It's usually not difficult to deal with - simply keep a Set of already-visited source objects - but often forgotten.

You probably should write a deep-merge function that only expects primitive values and simple objects - at most those types that the structured clone algorithm can handle - as merge sources. Throw if it encounters anything it cannot handle or just assign by reference instead of deep merging.

In other words, there is no one-size-fits-all algorithm, you either have to roll your own or look for a library method that happens to cover your use-cases.

3
  • 4
    excuses for V8 devs to not implement a secure "document state" transfer
    – neaumusic
    Commented Nov 3, 2016 at 18:21
  • 3
    You raise many good issues and I would have loved to see an implementation of your recommendation. So I tried to make one below. Could you please have a look and comment? stackoverflow.com/a/48579540/8122487
    – RaphaMex
    Commented Feb 3, 2018 at 2:36
  • Another thing to keep in mind: The solutions on this page are vulnerable to prototype pollution: learn.snyk.io/lessons/prototype-pollution/javascript Many existing libraries are protected against this.
    – Cyral
    Commented May 4, 2022 at 1:03
113

Update 2022:

I created mergician to address the various merge/clone requirements discussed in the comments and handle more advanced scenarios. It is based on the same concept as my original answer (below) but offers a more robust solution when needed:

Unlike native methods and other utilities, Mergician faithfully clones and merges objects by properly handling descriptor values, accessor functions, and prototype properties while offering advanced options for customizing the clone/merge process.

Notably, mergician is significantly smaller (1.5k min+gzip) than similar utilities like lodash.merge (5.1k min+gzip).


Original answer:

Since this issue is still active, here's another approach:

  • ES6/2015
  • Immutable (does not modify original objects)
  • Handles arrays (concatenates them)

/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
  const isObject = obj => obj && typeof obj === 'object';
  
  return objects.reduce((prev, obj) => {
    Object.keys(obj).forEach(key => {
      const pVal = prev[key];
      const oVal = obj[key];
      
      if (Array.isArray(pVal) && Array.isArray(oVal)) {
        prev[key] = pVal.concat(...oVal);
      }
      else if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeep(pVal, oVal);
      }
      else {
        prev[key] = oVal;
      }
    });
    
    return prev;
  }, {});
}

// Test objects
const obj1 = {
  a: 1,
  b: 1, 
  c: { x: 1, y: 1 },
  d: [ 1, 1 ]
}
const obj2 = {
  b: 2, 
  c: { y: 2, z: 2 },
  d: [ 2, 2 ],
  e: 2
}
const obj3 = mergeDeep(obj1, obj2);

// Out
console.log(obj3);

9
  • This is nice. However when we have array with repeated elements these are concatenated (there are repeated elements). I adapted this to take a parameter (arrays unique: true/false).
    – Astronaut
    Commented Jun 7, 2018 at 13:46
  • 6
    To make the arrays unique you can change prev[key] = pVal.concat(...oVal); to prev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index); Commented Jun 12, 2018 at 13:35
  • 3
    Glorious. This one demonstrates also that arrays get merged, which is what I was looking for. Commented Nov 13, 2019 at 11:01
  • 6
    Alternative es6 solution for unique arrays. Change prev[key] = pVal.concat(...oVal); to prev[key] = [...new Set([...oVal, ...pVal])]; Reference: stackoverflow.com/a/9229821/6671505
    – Davey
    Commented Mar 30, 2021 at 8:46
  • 1
    I read all the answers, this library is the most suitable for me, and there are suitable configuration items, which can be configured according to the actual situation. The only regret is that there is no typescript support, I hope it will be improved in the future. thanks
    – Simon
    Commented Nov 7, 2022 at 17:27
110

Here is an immutable (does not modify the inputs) version of @Salakar's answer. Useful if you're doing functional programming type stuff.

export function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

export default function mergeDeep(target, source) {
  let output = Object.assign({}, target);
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach(key => {
      if (isObject(source[key])) {
        if (!(key in target))
          Object.assign(output, { [key]: source[key] });
        else
          output[key] = mergeDeep(target[key], source[key]);
      } else {
        Object.assign(output, { [key]: source[key] });
      }
    });
  }
  return output;
}
11
  • 1
    @torazaburo see previous post by me for the isObject function
    – Salakar
    Commented May 18, 2016 at 20:45
  • 3
    Its a computed property name, the first will use the value of key as the property name, the later will make "key" the property name. See: es6-features.org/#ComputedPropertyNames
    – CpILL
    Commented Jun 20, 2016 at 13:16
  • 2
    in isObject you don't need to check && item !== null at the end, because the line starts with item &&, no?
    – ephemer
    Commented Nov 14, 2016 at 15:45
  • 7
    If source has nested child objects deeper than target, those objects will still reference the same values in mergedDeep's output (I think). E.g. const target = { a: 1 }; const source = { b: { c: 2 } }; const merged = mergeDeep(target, source); merged.b.c; // 2 source.b.c = 3; merged.b.c; // 3 Is this an issue? It doesn't mutate the inputs, but any future mutations to the inputs could mutate the output, and vice versa w/ mutations to output mutating inputs. For what it's worth, though, ramda's R.merge() has the same behavior. Commented Dec 26, 2016 at 20:03
  • 1
    yeah, this merges keys not values unless its a dict. Feel free to update the answer
    – CpILL
    Commented Jun 7, 2021 at 23:15
48

I know there's a lot of answers already and as many comments arguing they won't work. The only consensus is that it's so complicated that nobody made a standard for it. However, most of accepted answers in SO expose "simple tricks" that are widely used. So, for all of us like me who are no experts but want to write safer code by grasping a little more about javascript's complexity, I'll try to shed some light.

Before getting our hands dirty, let me clarify 2 points:

  • [DISCLAIMER] I propose a function below that tackles how we deep loop into javascript objects for copy and illustrates what is generally too shortly commented. It is not production-ready. For sake of clarity, I have purposedly left aside other considerations like circular objects (track by a set or unconflicting symbol property), copying reference value or deep clone, immutable destination object (deep clone again?), case-by-case study of each type of objects, get/set properties via accessors... Also, I did not test performance -although it's important- because it's not the point here either.
  • I'll use copy or assign terms instead of merge. Because in my mind a merge is conservative and should fail upon conflicts. Here, when conflicting, we want the source to overwrite the destination. Like Object.assign does.

Answers with for..in or Object.keys are misleading

Making a deep copy seems so basic and common practice that we expect to find a one-liner or, at least, a quick win via simple recursion. We don't expect we should need a library or write a custom function of 100 lines.

When I first read Salakar's answer, I genuinely thought I could do better and simpler (you can compare it with Object.assign on x={a:1}, y={a:{b:1}}). Then I read the8472's answer and I thought... there is no getting away so easily, improving already given answers won't get us far.

Let's let deep copy and recursive aside an instant. Just consider how (wrongly) people parse properties to copy a very simple object.

const y = Object.create(
    { proto : 1 },
    { a: { enumerable: true, value: 1},
      [Symbol('b')] : { enumerable: true, value: 1} } )

Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied

((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!

((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!

Object.keys will omit own non-enumerable properties, own symbol-keyed properties and all prototype's properties. It may be fine if your objects don't have any of those. But keep it mind that Object.assign handles own symbol-keyed enumerable properties. So your custom copy lost its bloom.

for..in will provide properties of the source, of its prototype and of the full prototype chain without you wanting it (or knowing it). Your target may end up with too many properties, mixing up prototype properties and own properties.

If you're writing a general purpose function and you're not using Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbols or Object.getPrototypeOf, you're most probably doing it wrong.

Things to consider before writing your function

First, make sure you understand what a Javascript object is. In Javascript, an object is made of its own properties and a (parent) prototype object. The prototype object in turn is made of its own properties and a prototype object. And so on, defining a prototype chain.

A property is a pair of key (string or symbol) and descriptor (value or get/set accessor, and attributes like enumerable).

Finally, there are many types of objects. You may want to handle differently an object Object from an object Date or an object Function.

So, writing your deep copy, you should answer at least those questions:

  1. What do I consider deep (proper for recursive look up) or flat?
  2. What properties do I want to copy? (enumerable/non-enumerable, string-keyed/symbol-keyed, own properties/prototype's own properties, values/descriptors...)

For my example, I consider that only the object Objects are deep, because other objects created by other constructors may not be proper for an in-depth look. Customized from this SO.

function toType(a) {
    // Get fine type (object, array, function, null, error, date ...)
    return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}

function isDeepObject(obj) {
    return "Object" === toType(obj);
}

And I made an options object to choose what to copy (for demo purpose).

const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};

Proposed function

You can test it in this plunker.

function deepAssign(options) {
    return function deepAssignWithOptions (target, ...sources) {
        sources.forEach( (source) => {

            if (!isDeepObject(source) || !isDeepObject(target))
                return;

            // Copy source's own properties into target's own properties
            function copyProperty(property) {
                const descriptor = Object.getOwnPropertyDescriptor(source, property);
                //default: omit non-enumerable properties
                if (descriptor.enumerable || options.nonEnum) {
                    // Copy in-depth first
                    if (isDeepObject(source[property]) && isDeepObject(target[property]))
                        descriptor.value = deepAssign(options)(target[property], source[property]);
                    //default: omit descriptors
                    if (options.descriptors)
                        Object.defineProperty(target, property, descriptor); // shallow copy descriptor
                    else
                        target[property] = descriptor.value; // shallow copy value only
                }
            }

            // Copy string-keyed properties
            Object.getOwnPropertyNames(source).forEach(copyProperty);

            //default: omit symbol-keyed properties
            if (options.symbols)
                Object.getOwnPropertySymbols(source).forEach(copyProperty);

            //default: omit prototype's own properties
            if (options.proto)
                // Copy souce prototype's own properties into target prototype's own properties
                deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
                    Object.getPrototypeOf(target),
                    Object.getPrototypeOf(source)
                );

        });
        return target;
    }
}

That can be used like this:

const x = { a: { a: 1 } },
      y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
2
  • Thank God! This works for me! Am I correct to state that "target is always the "smaller" object"? Thanks Commented Jan 14, 2022 at 7:22
  • This is probably my favorite answer as it takes into account all environmental factors. However, there is a bug in the code you should be aware of. Essentially if the target has a non-"deep"-object property but the source's is, it doesn't deep clone the source and so modifying the source ends up modifying the target also. Can be fixed fairly easily. Also, not a fan options model. const target = { foo: 4 }; const source = { foo: { bar: 5 } }; deepAssign({})(target, source); console.log(target); // target.foo.bar === 5 source.foo.bar = 6; console.log(target); // target.foo.bar === 6
    – Agendum
    Commented Mar 11, 2023 at 23:40
43

If you want to have a one liner without requiring a huge library like lodash, I suggest you to use deepmerge (npm install deepmerge) or deepmerge-ts (npm install deepmerge-ts).

deepmerge also comes with typings for TypeScript and is more stable (since it's older), but deepmerge-ts is also available for Deno and is faster by design, although written in TypeScript as the name implies.

Once imported you can do

deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });

to get

{ a: 2, b: 2, c: 3, d: 3 }

This works nicely with complex objects and arrays. A real all-rounder solution this is.

5
  • 2
    Been looking for hours, this saved the day, was able to merge deep objects, and as you said an all-rounder, cheers! Commented Sep 8, 2020 at 16:00
  • 12
    You don't have to require the entire lodash library. You can require just the parts you need: const merge = require('lodash.merge');
    – barney765
    Commented Dec 15, 2020 at 14:00
  • looked into their source code and didn't like their typings as they used as / any Commented Nov 24, 2022 at 12:22
  • @godblessstrawberry I agree. Since deepmerge-ts is preferred, I decided to open an issue about it. The usage of any is very minimal, but should be avoided. The older deepmerge should not be bothered, since it would only be preferred for JavaScript environments. Commented Nov 26, 2022 at 13:27
  • Thank you for this deepmerge-ts lib. I'm using it now in my deno.jsonc config file update cli: github.com/codemonument/deno_update_denoconfig Commented Mar 29 at 19:10
20

Here, straight forward;

a simple solution that works like Object.assign just deep, and works for an array, without need any modification.

function deepAssign(target, ...sources) {
  for (source of sources) {
    for (let k in source) {
      let vs = source[k], vt = target[k]
      if (Object(vs) == vs && Object(vt) === vt) {
        target[k] = deepAssign(vt, vs)
        continue
      }
      target[k] = source[k]
    }
  }
  return target
}

x = { a: { a: 1 }, b: [1,2] }
y = { a: { b: 1 }, b: [3] }
z = { c: 3, b: [,,,4] }
x = deepAssign(x, y, z)

console.log(JSON.stringify(x) === JSON.stringify({
  "a": {
    "a": 1,
    "b": 1
  },
  "b": [ 1, 2, null, 4 ],
  "c": 3
}))

Edit: I answer somewhere else about a new method for deep comparing 2 objects. that method can use also for a deep merging. If you want implantation put a comment https://stackoverflow.com/a/71177790/1919821

4
  • You should use more types varieties in your test case (new Date(), a(){}, null, undefined, 0).
    – vsync
    Commented Oct 29, 2020 at 10:35
  • Found major bug - jsbin.com/javefudife/1/edit?html,js,console
    – vsync
    Commented Oct 29, 2020 at 11:11
  • what the bug? the example you use is very simple. and checking it in the console brings the right result . jsbin look buggi
    – pery mimon
    Commented Nov 2, 2020 at 12:27
  • 1
    Throws an error: Object is not iterable with this given input: { "CommandWorkflows": { "businessRules": [{ "arrayParsing": [{ "characterArrayParsing": [{ "Workflow": [{ "$": { "Name": "doesArrayContainCharacter", "Value": "cmdgen bizRul,doesArrayContainCharacter,$,[the|answer|to|life|the|universe|and|everything|is|$42] 4"}}]}]}]}]}}
    – Seth Eden
    Commented May 24, 2022 at 1:19
18

Many answers use tens of lines of code, or require adding a new library to the project, but if you use recursion, this is just 4 lines of code.

function merge(current, updates) {
  for (key of Object.keys(updates)) {
    if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
    else merge(current[key], updates[key]);
  }
  return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));

Arrays handling: The above version overwrites old array values with new ones. If you want it to keep the old array values and add the new ones, just add a else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key]) block above the else statament and you're all set.

1
  • 2
    I like it but it needs a simple undefined check for 'current' or else {foo: undefined} does not merge. Just add an if(current) before the for loop. Commented Jan 5, 2020 at 10:26
15

Here is TypeScript implementation:

export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T  => {
  if (!sources.length) {
    return target;
  }
  const source = sources.shift();
  if (source === undefined) {
    return target;
  }

  if (isMergebleObject(target) && isMergebleObject(source)) {
    Object.keys(source).forEach(function(key: string) {
      if (isMergebleObject(source[key])) {
        if (!target[key]) {
          target[key] = {};
        }
        mergeObjects(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    });
  }

  return mergeObjects(target, ...sources);
};

const isObject = (item: any): boolean => {
  return item !== null && typeof item === 'object';
};

const isMergebleObject = (item): boolean => {
  return isObject(item) && !Array.isArray(item);
};

And Unit Tests:

describe('merge', () => {
  it('should merge Objects and all nested Ones', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
    const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
    const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
    expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
  });
  it('should behave like Object.assign on the top level', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C'};
    const obj2 = { a: undefined, b: { b1: 'B1'}};
    expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
  });
  it('should not merge array values, just override', () => {
    const obj1 = {a: ['A', 'B']};
    const obj2 = {a: ['C'], b: ['D']};
    expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
  });
  it('typed merge', () => {
    expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
      .toEqual(new TestPosition(1, 1));
  });
});

class TestPosition {
  constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}
12

The deepmerge npm package appears to be the most widely used library for solving this problem: https://www.npmjs.com/package/deepmerge

0
11

I would like to present a pretty simple ES5 alternative. The function gets 2 parameters - target and source that must be of type "object". Target will be the resulting object. Target keeps all its original properties but their values may be modified though.

function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
  if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
  if(prop in target) { // handling merging of two properties with equal names
    if(typeof target[prop] !== 'object') {
      target[prop] = source[prop];
    } else {
      if(typeof source[prop] !== 'object') {
        target[prop] = source[prop];
      } else {
        if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
          target[prop] = target[prop].concat(source[prop]);
        } else { // two objects get merged recursively
          target[prop] = deepMerge(target[prop], source[prop]); 
        } 
      }  
    }
  } else { // new properties get added to target
    target[prop] = source[prop]; 
  }
}
return target;
}

cases:

  • if target doesn't have a source property, target gets it;
  • if target does have a source property and target & source are not both objects (3 cases out of 4), target's property gets overriden;
  • if target does have a source property and both of them are objects/arrays (1 remaining case), then recursion happens merging two objects (or concatenation of two arrays);

also consider the following:

  1. array + obj = array
  2. obj + array = obj
  3. obj + obj = obj (recursively merged)
  4. array + array = array (concat)

It is predictable, supports primitive types as well as arrays and objects. Also as we can merge 2 objects, I think that we can merge more than 2 via reduce function.

take a look at an example (and play around with it if you want):

var a = {
   "a_prop": 1,
   "arr_prop": [4, 5, 6],
   "obj": {
     "a_prop": {
       "t_prop": 'test'
     },
     "b_prop": 2
   }
};

var b = {
   "a_prop": 5,
   "arr_prop": [7, 8, 9],
   "b_prop": 15,
   "obj": {
     "a_prop": {
       "u_prop": false
     },
     "b_prop": {
        "s_prop": null
     }
   }
};

function deepMerge(target, source) {
    if(typeof target !== 'object' || typeof source !== 'object') return false;
    for(var prop in source) {
    if(!source.hasOwnProperty(prop)) continue;
      if(prop in target) {
        if(typeof target[prop] !== 'object') {
          target[prop] = source[prop];
        } else {
          if(typeof source[prop] !== 'object') {
            target[prop] = source[prop];
          } else {
            if(target[prop].concat && source[prop].concat) {
              target[prop] = target[prop].concat(source[prop]);
            } else {
              target[prop] = deepMerge(target[prop], source[prop]); 
            } 
          }  
        }
      } else {
        target[prop] = source[prop]; 
      }
    }
  return target;
}

console.log(deepMerge(a, b));

There is a limitation - browser's call stack length. Modern browsers will throw an error at some really deep level of recursion (think of thousands of nested calls). Also you are free to treat situations like array + object etc. as you wish by adding new conditions and type checks.

0
8

Is there a way to do this?

If npm libraries can be used as a solution, object-merge-advanced from yours truly allows to merge objects deeply and customise/override every single merge action using a familiar callback function. The main idea of it is more than just deep merging — what happens with the value when two keys are the same? This library takes care of that — when two keys clash, object-merge-advanced weighs the types, aiming to retain as much data as possible after merging:

object key merging weighing key value types to retain as much data as possible

First input argument's key is marked #1, second argument's — #2. Depending on each type, one is chosen for the result key's value. In diagram, "an object" means a plain object (not array etc).

When keys don't clash, they all enter the result.

From your example snippet, if you used object-merge-advanced to merge your code snippet:

const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
//      a: {
//        a: 1,
//        b: 1
//      }
//    }

It's algorithm recursively traverses all input object keys, compares and builds and returns the new merged result.

1
  • where are dates and functions in this table infographic?
    – vsync
    Commented Oct 29, 2020 at 10:26
7

A simple solution with ES5 (overwrite existing value):

function merge(current, update) {
  Object.keys(update).forEach(function(key) {
    // if update[key] exist, and it's not a string or array,
    // we go in one level deeper
    if (current.hasOwnProperty(key) 
        && typeof current[key] === 'object'
        && !(current[key] instanceof Array)) {
      merge(current[key], update[key]);

    // if update[key] doesn't exist in current, or it's a string
    // or array, then assign/overwrite current[key] to update[key]
    } else {
      current[key] = update[key];
    }
  });
  return current;
}

var x = { a: { a: 1 } }
var y = { a: { b: 1 } }

console.log(merge(x, y));

1
  • just what i needed - es6 was causing problems in build - this es5 alternative is the bomb
    – danday74
    Commented Aug 20, 2018 at 13:36
6

The following function makes a deep copy of objects, it covers copying primitive, arrays as well as object

 function mergeDeep (target, source)  {
    if (typeof target == "object" && typeof source == "object") {
        for (const key in source) {
            if (source[key] === null && (target[key] === undefined || target[key] === null)) {
                target[key] = null;
            } else if (source[key] instanceof Array) {
                if (!target[key]) target[key] = [];
                //concatenate arrays
                target[key] = target[key].concat(source[key]);
            } else if (typeof source[key] == "object") {
                if (!target[key]) target[key] = {};
                this.mergeDeep(target[key], source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
6

Most examples here seem too complex, I'm using one in TypeScript I created, I think it should cover most cases (I'm handling arrays as regular data, just replacing them).

const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);

export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
  const isDeep = (prop: string) =>
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...(target as Object),
    ...(replaced as Object)
  } as A & B;
};

Same thing in plain JS, just in case:

const isObject = item => typeof item === 'object' && !Array.isArray(item);

const merge = (target, source) => {
  const isDeep = prop => 
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...target,
    ...replaced
  };
};

Here are my test cases to show how you could use it

describe('merge', () => {
  context('shallow merges', () => {
    it('merges objects', () => {
      const a = { a: 'discard' };
      const b = { a: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test' });
    });
    it('extends objects', () => {
      const a = { a: 'test' };
      const b = { b: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
    });
    it('extends a property with an object', () => {
      const a = { a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
    it('replaces a property with an object', () => {
      const a = { b: 'whatever', a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
  });

  context('deep merges', () => {
    it('merges objects', () => {
      const a = { test: { a: 'discard', b: 'test' }  };
      const b = { test: { a: 'test' } } ;
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends objects', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: 'test' } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends a property with an object', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
    it('replaces a property with an object', () => {
      const a = { test: { b: 'whatever', a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
  });
});

Please let me know if you think I'm missing some functionality.

0
6

I didn't like any of the existing solutions. So, I went ahead and wrote my own.

Object.prototype.merge = function(object) {
    for (const key in object) {
        if (object.hasOwnProperty(key)) {
            if (typeof this[key] == 'object' && typeof object[key] == 'object') {
                this[key].merge(object[key]);
                continue;
            }

            this[key] = object[key];
        }
    }

    return this;
}

It would be used like this:

const object = {
    health: 100,
    position: {
        x: 0,
        y: 10
    }
};

object.merge({
    health: 99,
    position: {
        x: 10
    },
    extension: null
});

Which results in:

{
    health: 99,
    position: {
        x: 10,
        y: 10
    }
}

Update

I have reworked a few things to prevent accidents.

Object.defineProperty(Object, 'merge', {
    value: function merge(target, ...sources) {
        if (typeof target == 'undefined') {
            throw new TypeError("Cannot convert undefined or null to object");
        } else if (Array.isArray(target)) {
            target.push(...sources.flat());
            return target;
        }
        for (const source of sources) {
            for (const key in source) {
                if (!source.hasOwnProperty(key)) continue;
                if (typeof target[key] == 'object' && target[key] !== null && typeof source[key] == 'object') {
                    Object.merge(target[key], source[key]);
                    continue;
                } else if (Array.isArray(target[key]) && Array.isArray(source[key])) {
                    target[key] = target[key].concat(source[key]);
                    continue;
                }

                target[key] = source[key];
            }
        }

        return target;
    },
    writable: true
});

To avoid messing with every object's prototype, I added the utility function as a method of Object. Follow the example for usage.

let object = {
    key: 'value',
    property: {
        subKey: 'subValue',
        subProperty: {
            subPropertyKey: 'subPropertyValue'
        }
    }
};

Object.merge(object, {
    key: 'newValue'
}, {
    property: {
        subKey: 'newValue'
    }
}, {
    property: {
        newSubKey: 'newSubValue',
        subProperty: {
            subPropertyKey: 'newSubPropertyValue'
        }
    }
});

As a result, the following object is produced.

{
    key: "newValue",
    property: {
        newSubKey: "newSubValue",
        subKey: "newValue",
        subProperty: {
            subPropertyKey: "newSubPropertyValue"
        }
    }
}

It is essentially Object.assign with more depth and security.

4
  • This will merge only properties existing in this, maybe this.hasOwnProperty(key) should be object.hasOwnProperty(key) Commented Sep 4, 2021 at 10:34
  • @GiulianoCollacchioni Good catch! I was really tired when I made this, I wasn't really thinking with my brain. Commented Sep 5, 2021 at 16:36
  • It still can't copy functions. Commented Apr 15, 2023 at 19:08
  • @VishalKumarSahu That's the point! Functions wouldn't necessarily need to be copied in any circumstance. But if you do have a reason for it, you can replace whatever is in the if statement that checks whether it's an object with a check with the function being in the original object's prototype. Something like this: (typeof this[key] == 'object' && typeof object[key] == 'object') || (typeof object[key] == 'function' && !(key in Reflect.getPrototypeOf(this))). Commented May 23, 2023 at 10:31
5

If your are using ImmutableJS you can use mergeDeep :

fromJS(options).mergeDeep(options2).toJS();
0
5

with reduce

export const merge = (objFrom, objTo) => Object.keys(objFrom)
    .reduce(
        (merged, key) => {
            merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key])
                ? merge(objFrom[key], merged[key] ?? {})
                : objFrom[key]
            return merged
        }, { ...objTo }
    )
test('merge', async () => {
    const obj1 = { par1: -1, par2: { par2_1: -21, par2_5: -25 }, arr: [0,1,2] }
    const obj2 = { par1: 1, par2: { par2_1: 21 }, par3: 3, arr: [3,4,5] }
    const obj3 = merge3(obj1, obj2)
    expect(obj3).toEqual(
        { par1: -1, par2: { par2_1: -21, par2_5: -25 }, par3: 3, arr: [0,1,2] }
    )
})
4

We can use $.extend(true,object1,object2) for deep merging. Value true denotes merge two objects recursively, modifying the first.

$extend(true,target,object)

3
  • 14
    The asker never indicated that they are using jquery and appears to be asking for a native javascript solution.
    – Teh JoE
    Commented Mar 27, 2017 at 15:31
  • This is a very simple way of doing this and it works. A viable solution that I would consider if I was the one asking this question. :)
    – kashiraja
    Commented Dec 8, 2017 at 18:40
  • This is a very good answer but is missing a link to the source code to jQuery. jQuery has a lot of people working on the project and they have spent some time getting deep copying working properly. Also, the source code is fairly "simple": github.com/jquery/jquery/blob/master/src/core.js#L125 "Simple" is in quotes because it starts getting complicated when digging into jQuery.isPlainObject(). That exposes the complexity of determining whether or not something is a plain object, which most of the answers here miss by a long shot. Guess what language jQuery is written in? Commented Mar 26, 2019 at 12:52
4

Ramda which is a nice library of javascript functions has mergeDeepLeft and mergeDeepRight. Any of these work pretty well for this problem. Please take a look on the documentation here: https://ramdajs.com/docs/#mergeDeepLeft

For the specific example in question we can use:

import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}
3

I was having this issue when loading a cached redux state. If I just load the cached state, I'd run into errors for new app version with an updated state structure.

It was already mentioned, that lodash offers the merge function, which I used:

const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);
3

New Method | Updated Answer

As of node v17, there is structuredClone that according to reference:

creates a deep clone of a given value using the structured clone algorithm.

So, we can use it like this to merge 2 objects:

const deepMerge = (obj1, obj2) => {
  const clone1 = structuredClone(obj1);
  const clone2 = structuredClone(obj2);

  for (let key in clone2) {
    if (clone2[key] instanceof Object && clone1[key] instanceof Object) {
      clone1[key] = deepMerge(clone1[key], clone2[key]);
    } else {
      clone1[key] = clone2[key];
    }
  }

  return clone1;
};


const first = { a: { x: 'x', y: 'y' }, b: 1 };
const second = { a: { x: 'xx' }, c: 2 };

const result = deepMerge(first, second);

console.log(result); // { a: { x: 'xx', y: 'y' }, b: 1, c: 2 }

2

Here's another one I just wrote that supports arrays. It concats them.

function isObject(obj) {
    return obj !== null && typeof obj === 'object';
}


function isPlainObject(obj) {
    return isObject(obj) && (
        obj.constructor === Object  // obj = {}
        || obj.constructor === undefined // obj = Object.create(null)
    );
}

function mergeDeep(target, ...sources) {
    if (!sources.length) return target;
    const source = sources.shift();

    if(Array.isArray(target)) {
        if(Array.isArray(source)) {
            target.push(...source);
        } else {
            target.push(source);
        }
    } else if(isPlainObject(target)) {
        if(isPlainObject(source)) {
            for(let key of Object.keys(source)) {
                if(!target[key]) {
                    target[key] = source[key];
                } else {
                    mergeDeep(target[key], source[key]);
                }
            }
        } else {
            throw new Error(`Cannot merge object with non-object`);
        }
    } else {
        target = source;
    }

    return mergeDeep(target, ...sources);
};
2

Use this function:

merge(target, source, mutable = false) {
        const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
        for (const prop in source) {
            if (target[prop] == null || typeof target[prop] === 'undefined') {
                newObj[prop] = source[prop];
            } else if (Array.isArray(target[prop])) {
                newObj[prop] = source[prop] || target[prop];
            } else if (target[prop] instanceof RegExp) {
                newObj[prop] = source[prop] || target[prop];
            } else {
                newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
            }
        }
        return newObj;
    }
2

This is a cheap deep merge that uses as little code as I could think of. Each source overwrites the previous property when it exists.

const { keys } = Object;

const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
  isObject(a) && isObject(b)
    ? deepMerge(a, b)
    : isObject(a) && !isObject(b)
    ? a
    : b;

const coalesceByKey = source => (acc, key) =>
  (acc[key] && source[key]
    ? (acc[key] = merge(acc[key], source[key]))
    : (acc[key] = source[key])) && acc;

/**
 * Merge all sources into the target
 * overwriting primitive values in the the accumulated target as we go (if they already exist)
 * @param {*} target
 * @param  {...any} sources
 */
const deepMerge = (target, ...sources) =>
  sources.reduce(
    (acc, source) => keys(source).reduce(coalesceByKey(source), acc),
    target
  );

console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));
2
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
  for (const prop in source) {
    if (!source.hasOwnProperty(prop)) {
      continue;
    }

    if (source[prop] === null) {
      // property is null
      dest[prop] = source[prop];
      continue;
    }

    if (typeof source[prop] === 'object') {
      // if property is object let's dive into in
      if (Array.isArray(source[prop])) {
        dest[prop] = [];
      } else {
        if (!dest.hasOwnProperty(prop)
        || typeof dest[prop] !== 'object'
        || dest[prop] === null || Array.isArray(dest[prop])
        || !Object.keys(dest[prop]).length) {
          dest[prop] = {};
        }
      }
      recursivelyMoveProperties(source[prop], dest[prop]);
      continue;
    }

    // property is simple type: string, number, e.t.c
    dest[prop] = source[prop];
  }
  return dest;
}

Unit test:

describe('recursivelyMoveProperties', () => {
    it('should copy properties correctly', () => {
      const source: any = {
        propS1: 'str1',
        propS2: 'str2',
        propN1: 1,
        propN2: 2,
        propA1: [1, 2, 3],
        propA2: [],
        propB1: true,
        propB2: false,
        propU1: null,
        propU2: null,
        propD1: undefined,
        propD2: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subN1: 21,
          subN2: 22,
          subA1: [21, 22, 23],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      let dest: any = {
        propS2: 'str2',
        propS3: 'str3',
        propN2: -2,
        propN3: 3,
        propA2: [2, 2],
        propA3: [3, 2, 1],
        propB2: true,
        propB3: false,
        propU2: 'not null',
        propU3: null,
        propD2: 'defined',
        propD3: undefined,
        propO2: {
          subS2: 'inv22',
          subS3: 'sub23',
          subN2: -22,
          subN3: 23,
          subA2: [5, 5, 5],
          subA3: [31, 32, 33],
          subB2: false,
          subB3: true,
          subU2: 'not null --- ',
          subU3: null,
          subD2: ' not undefined ----',
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      dest = recursivelyMoveProperties(source, dest);

      expect(dest).toEqual({
        propS1: 'str1',
        propS2: 'str2',
        propS3: 'str3',
        propN1: 1,
        propN2: 2,
        propN3: 3,
        propA1: [1, 2, 3],
        propA2: [],
        propA3: [3, 2, 1],
        propB1: true,
        propB2: false,
        propB3: false,
        propU1: null,
        propU2: null,
        propU3: null,
        propD1: undefined,
        propD2: undefined,
        propD3: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subS3: 'sub23',
          subN1: 21,
          subN2: 22,
          subN3: 23,
          subA1: [21, 22, 23],
          subA2: [],
          subA3: [31, 32, 33],
          subB1: false,
          subB2: true,
          subB3: true,
          subU1: null,
          subU2: null,
          subU3: null,
          subD1: undefined,
          subD2: undefined,
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      });
    });
  });
2

Another variation using recursion, hope you find it useful.

const merge = (obj1, obj2) => {

    const recursiveMerge = (obj, entries) => {
         for (const [key, value] of entries) {
            if (typeof value === "object") {
               obj[key] = obj[key] ? {...obj[key]} : {};
               recursiveMerge(obj[key], Object.entries(value))
            else {
               obj[key] = value;
            }
          }

          return obj;
    }

    return recursiveMerge(obj1, Object.entries(obj2))
}
0
2

My use case for this was to merge default values into a configuration. If my component accepts a configuration object that has a deeply nested structure, and my component defines a default configuration, I wanted to set default values in my configuration for all configuration options that were not supplied.

Example usage:

export default MyComponent = ({config}) => {
  const mergedConfig = mergeDefaults(config, {header:{margins:{left:10, top: 10}}});
  // Component code here
}

This allows me to pass an empty or null config, or a partial config and have all of the values that are not configured fall back to their default values.

My implementation of mergeDefaults looks like this:

export default function mergeDefaults(config, defaults) {
  if (config === null || config === undefined) return defaults;
  for (var attrname in defaults) {
    if (defaults[attrname].constructor === Object) config[attrname] = mergeDefaults(config[attrname], defaults[attrname]);
    else if (config[attrname] === undefined) config[attrname] = defaults[attrname];
  }
  return config;
}


And these are my unit tests

import '@testing-library/jest-dom/extend-expect';
import mergeDefaults from './mergeDefaults';

describe('mergeDefaults', () => {
  it('should create configuration', () => {
    const config = mergeDefaults(null, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(10);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('default2');
  });
  it('should fill configuration', () => {
    const config = mergeDefaults({}, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(10);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('default2');
  });
  it('should not overwrite configuration', () => {
    const config = mergeDefaults({ a: 12, b: { c: 'config1', d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(12);
    expect(config.b.c).toStrictEqual('config1');
    expect(config.b.d).toStrictEqual('config2');
  });
  it('should merge configuration', () => {
    const config = mergeDefaults({ a: 12, b: { d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' }, e: 15 });
    expect(config.a).toStrictEqual(12);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('config2');
    expect(config.e).toStrictEqual(15);
  });
});

1

Sometimes you don't need deep merge, even if you think so. For example, if you have a default config with nested objects and you want to extend it deeply with your own config, you can create a class for that. The concept is very simple:

function AjaxConfig(config) {

  // Default values + config

  Object.assign(this, {
    method: 'POST',
    contentType: 'text/plain'
  }, config);

  // Default values in nested objects

  this.headers = Object.assign({}, this.headers, { 
    'X-Requested-With': 'custom'
  });
}

// Define your config

var config = {
  url: 'https://google.com',
  headers: {
    'x-client-data': 'CI22yQEI'
  }
};

// Extend the default values with your own
var fullMergedConfig = new AjaxConfig(config);

// View in DevTools
console.log(fullMergedConfig);

You can convert it to a function (not a constructor).

Not the answer you're looking for? Browse other questions tagged or ask your own question.