deep merge objects with AngularJS
Normally to shallow copy objects I would use angular.extend()
Here's an example of that:
var object1 = {
"key": "abc123def456",
"message": {
"subject": "Has a Question",
"from": "[email protected]",
"to": "[email protected]"
}
};
var object2 = {
"key": "00700916391"
};
console.log(angular.extend({}, object1, object2));
Would give us:
{
"key": "00700916391",
"message": {
"subject": "Has a Question",
"from": "[email protected]",
"to": "[email protected]"
}
}
But what if I wanted to merge objects so that parent keys are not over written by child objects:
var object1 = {
"key": "abc123def456",
"message": {
"subject": "Has a Question",
"from": "[email protected]",
"to": "[email protected]"
}
};
var object2 = {
"key": "00700916391", //Overwrite me
"message": { //Dont overwrite me!
"subject": "Hey what's up?", //Overwrite me
"something": "something new" //Add me
}
};
console.log(merge(object1, object2));
Would give us:
{
"key": "00700916391",
"message": {
"subject": "Hey what's up?",
"from": "[email protected]",
"to": "[email protected]",
"something": "something new"
}
}
Is there an Angular function that already does a deep merge that I am not aware of?
If not is there a native way to do this in javascript recursively for n levels deep?
Solution 1:
Angular 1.4 or later
Use angular.merge
:
Unlike
extend()
,merge()
recursively descends into object properties of source objects, performing a deep copy.
angular.merge(object1, object2); // merge object 2 into object 1
Older versions of Angular:
There is no reason a simple recursive algorithm shouldn't work :)
Assuming they're both the result of JSON.stringify or similar:
function merge(obj1,obj2){ // Our merge function
var result = {}; // return result
for(var i in obj1){ // for every property in obj1
if((i in obj2) && (typeof obj1[i] === "object") && (i !== null)){
result[i] = merge(obj1[i],obj2[i]); // if it's an object, merge
}else{
result[i] = obj1[i]; // add it to result
}
}
for(i in obj2){ // add the remaining properties from object 2
if(i in result){ //conflict
continue;
}
result[i] = obj2[i];
}
return result;
}
Here is a working fiddle
(Note, arrays are not handled here)
Solution 2:
In the new version of Angularjs they added merge function which will perform the deep copy.
For the older versions, I have created my custom function by copying the code of merge function from new version of Angularjs. Below is the code for the same,
function merge(dst){
var slice = [].slice;
var isArray = Array.isArray;
function baseExtend(dst, objs, deep) {
for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
if (!angular.isObject(obj) && !angular.isFunction(obj)) continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];
if (deep && angular.isObject(src)) {
if (!angular.isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
} else {
dst[key] = src;
}
}
}
return dst;
}
return baseExtend(dst, slice.call(arguments, 1), true);
}
Hope this will help someone who is wondering why angular.merge is not working in older versions.
Solution 3:
angular.merge polyfill for angular < 1.4.0
if (!angular.merge) {
angular.merge = (function mergePollyfill() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
} else {
delete obj.$$hashKey;
}
}
function baseExtend(dst, objs, deep) {
var h = dst.$$hashKey;
for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
if (!angular.isObject(obj) && !angular.isFunction(obj)) continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];
if (deep && angular.isObject(src)) {
if (angular.isDate(src)) {
dst[key] = new Date(src.valueOf());
} else {
if (!angular.isObject(dst[key])) dst[key] = angular.isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
}
} else {
dst[key] = src;
}
}
}
setHashKey(dst, h);
return dst;
}
return function merge(dst) {
return baseExtend(dst, [].slice.call(arguments, 1), true);
}
})();
}