I extended David Coallier's method:
- Added the possibility to merge multiple objects
- Supports deep objects
- override parameter (that's detected if the last parameter is a boolean)
If override is false, no property gets overridden but new properties will be added.
Usage: obj.merge(merges... [, override]);
Here is my code:
Object.defineProperty(Object.prototype, "merge", { enumerable: false, value: function () { var override = true, dest = this, len = arguments.length, props, merge, i, from; if (typeof(arguments[arguments.length - 1]) === "boolean") { override = arguments[arguments.length - 1]; len = arguments.length - 1; } for (i = 0; i < len; i++) { from = arguments[i]; if (from != null) { Object.getOwnPropertyNames(from).forEach(function (name) { var descriptor; // nesting if ((typeof(dest[name]) === "object" || typeof(dest[name]) === "undefined") && typeof(from[name]) === "object") { // ensure proper types (Array rsp Object) if (typeof(dest[name]) === "undefined") { dest[name] = Array.isArray(from[name]) ? [] : {}; } if (override) { if (!Array.isArray(dest[name]) && Array.isArray(from[name])) { dest[name] = []; } else if (Array.isArray(dest[name]) && !Array.isArray(from[name])) { dest[name] = {}; } } dest[name].merge(from[name], override); } // flat properties else if ((name in dest && override) || !(name in dest)) { descriptor = Object.getOwnPropertyDescriptor(from, name); if (descriptor.configurable) { Object.defineProperty(dest, name, descriptor); } } }); } } return this; } });
Examples and TestCases:
function clone (obj) { return JSON.parse(JSON.stringify(obj)); } var obj = { name : "trick", value : "value" }; var mergeObj = { name : "truck", value2 : "value2" }; var mergeObj2 = { name : "track", value : "mergeObj2", value2 : "value2-mergeObj2", value3 : "value3" }; assertTrue("Standard", clone(obj).merge(mergeObj).equals({ name : "truck", value : "value", value2 : "value2" })); assertTrue("Standard no Override", clone(obj).merge(mergeObj, false).equals({ name : "trick", value : "value", value2 : "value2" })); assertTrue("Multiple", clone(obj).merge(mergeObj, mergeObj2).equals({ name : "track", value : "mergeObj2", value2 : "value2-mergeObj2", value3 : "value3" })); assertTrue("Multiple no Override", clone(obj).merge(mergeObj, mergeObj2, false).equals({ name : "trick", value : "value", value2 : "value2", value3 : "value3" })); var deep = { first : { name : "trick", val : "value" }, second : { foo : "bar" } }; var deepMerge = { first : { name : "track", anotherVal : "wohoo" }, second : { foo : "baz", bar : "bam" }, v : "on first layer" }; assertTrue("Deep merges", clone(deep).merge(deepMerge).equals({ first : { name : "track", val : "value", anotherVal : "wohoo" }, second : { foo : "baz", bar : "bam" }, v : "on first layer" })); assertTrue("Deep merges no override", clone(deep).merge(deepMerge, false).equals({ first : { name : "trick", val : "value", anotherVal : "wohoo" }, second : { foo : "bar", bar : "bam" }, v : "on first layer" })); var obj1 = {a: 1, b: "hello"}; obj1.merge({c: 3}); assertTrue(obj1.equals({a: 1, b: "hello", c: 3})); obj1.merge({a: 2, b: "mom", d: "new property"}, false); assertTrue(obj1.equals({a: 1, b: "hello", c: 3, d: "new property"})); var obj2 = {}; obj2.merge({a: 1}, {b: 2}, {a: 3}); assertTrue(obj2.equals({a: 3, b: 2})); var a = []; var b = [1, [2, 3], 4]; a.merge(b); assertEquals(1, a[0]); assertEquals([2, 3], a[1]); assertEquals(4, a[2]); var o1 = {}; var o2 = {a: 1, b: {c: 2}}; var o3 = {d: 3}; o1.merge(o2, o3); assertTrue(o1.equals({a: 1, b: {c: 2}, d: 3})); o1.b.c = 99; assertTrue(o2.equals({a: 1, b: {c: 2}})); // checking types with arrays and objects var bo; a = []; bo = [1, {0:2, 1:3}, 4]; b = [1, [2, 3], 4]; a.merge(b); assertTrue("Array stays Array?", Array.isArray(a[1])); a = []; a.merge(bo); assertTrue("Object stays Object?", !Array.isArray(a[1])); a = []; a.merge(b); a.merge(bo); assertTrue("Object overrides Array", !Array.isArray(a[1])); a = []; a.merge(b); a.merge(bo, false); assertTrue("Object does not override Array", Array.isArray(a[1])); a = []; a.merge(bo); a.merge(b); assertTrue("Array overrides Object", Array.isArray(a[1])); a = []; a.merge(bo); a.merge(b, false); assertTrue("Array does not override Object", !Array.isArray(a[1]));
My equals method can be found here: Object comparison in JavaScript
var obj2 = { animal: 'dog', food: 'bone' };
, the merge would be{ food: 'bone', car: 'ford', animal: 'dog' }
. If you are working with "nested data", and want a "deep merge", then look for answers that mention "deep merge" or "recursion". If you have values that arearrays
, then use "arrayMerge" option of github "TehShrike/deepmerge", as mentioned here.