Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I merge an array of objects in Javascript?

Tags:

javascript

Example:

var array1 = [ {'key':1, 'property1': 'x'}, {'key':2, 'property1': 'y'} ]
var array2 = [ {'key':2, 'property2': 'a'}, {'key':1, 'property2': 'b'} ]

I want merge(array1, array2) to give me:

[
    {'key':1, 'property1': 'x', 'property2' : 'b'},
    {'key':2, 'property1': 'y', 'property2' : 'a'}
]

Is there an easy way to do this?

EDIT: several people have answered without looking too closely at my problem, please be note that I want to match similar objects in each array and combine their properties into my final array. Keys are unique and there will only ever be at most one object with a particular key in each array.

like image 209
user3409889 Avatar asked Jul 20 '14 03:07

user3409889


2 Answers

I wrote a quick not-so-quick solution. The one problem you might want to consider is whether a property from an object in the second array should override the same property, if present, in the second object it's being compared to.

Solution 1

This solution is of complexity O(n²). Solution 2 is much faster; this solution is only for those who don't want to be Sanic the Hedgehog fast.

JavaScript

var mergeByKey = function (arr1, arr2, key) {
// key is the key that the function merges based on
    arr1.forEach(function (d, i) {
        var prop = d[key];
        // since keys are unique, compare based on this key's value
        arr2.forEach(function (f) {
            if (prop == f[key]) {    // if true, the objects share keys
                for (var x in f) {   // loop through each key in the 2nd object
                    if (!(x in d))   // if the key is not in the 1st object
                        arr1[i][x] = f[x];    // add it to the first object
                // this is the part you might want to change for matching properties
                // which object overrides the other?
                }
            }
        })
    })
    return arr1;
}

Test Case

var arr = [ {'key':1, 'property1': 'x'},
            {'key':2, 'property1': 'y'} ],
    arr2= [ {'key':2, 'property2': 'a'},
            {'key':1, 'property2': 'b'} ];

console.log(mergeByKey(arr, arr2, "key"));

Results

/* returns:
    Object
    key: 1
    property1: "x"
    property2: "b"
    __proto__: Object
and 
    Object
    key: 2
    property1: "y"
    property2: "a"
    __proto__: Object
*/

fiddle

Solution 2

As Vivin Paliath pointed out in the comments below, my first solution was of O(n²) complexity (read: bad). His answer is very good and provides a solution with a complexity of O(m + n), where m is the size of the first array and n of the second array. In other words, of complexity O(2n).

However, his solution does not address objects within objects. To solve this, I used recursion—read: the devil, just like O(n²).

JavaScript

var mergeByKey = function (arr1, arr2, key) {
    var holder = [],
        storedKeys = {},
        i = 0; j = 0; l1 = arr1.length, l2 = arr2.length;

    var merge = function (obj, ref) {
        for (var x in obj) {
            if (!(x in ref || x instanceof Object)) {
                ref[x] = obj[x];
            } else {
                merge(obj[x], ref[x]);
            }
        }
        storedKeys[obj.key] = ref;
    }
    for (; i < l1; i++) {
        merge(arr1[i], storedKeys[arr1[i].key] || {});
    }
    for (; j < l2; j++) {
        merge(arr2[j], storedKeys[arr2[j].key] || {});
    }

    delete storedKeys[undefined];

    for (var obj in storedKeys)
        holder.push(storedKeys[obj]);

    return holder;
}

Test Case

var arr1 = [
    {
        "key" : 1,
        "prop1" : "x",
        "test" : {
            "one": 1,
            "test2": {
                "maybe" : false,
                "test3": { "nothing" : true }
            }
        }
    },
    {
        "key" : 2,
        "prop1": "y",
        "test" : { "one": 1 }
    }],
    arr2 = [
        {
            "key" : 1,
            "prop2" : "y",
            "test" : { "two" : 2 }
        },
        {
            "key" : 2,
            "prop2" : "z",
            "test" : { "two": 2 }
        }];
console.log(mergeByKey(arr1, arr2, "key"));

Results

/*
Object
    key: 1
    prop1: "x"
    prop2: "y"
    test: Object
        one: 1
        test2: Object
            maybe: false
            test3: Object
                nothing: true
                __proto__: Object
            __proto__: Object
        two: 2
        __proto__: Object
    __proto__: Object
Object
    key: 2
    prop1: "y"
    prop2: "z"
    test: Object
        one: 1
        two: 2
        __proto__: Object
    __proto__: Object
*/

This correctly merges the objects, along with all child objects. This solutions assumes that objects with matching keys have the same hierarchies. It also does not handle the merging of two arrays.

fiddle

like image 126
royhowie Avatar answered Sep 29 '22 14:09

royhowie


You could do something like this:

function merge(array1, array2) {
    var keyedResult = {};

    function _merge(element) {
        if(!keyedResult[element.key]) {
            keyedResult[element.key] = {};
        }

        var entry = keyedResult[element.key];
        for(var property in element) if(element.hasOwnProperty(property)) {
            if(property !== "key") {
                entry[property] = element[property];
            }                
        }

        entry["key"] = element.key;
    }

    array1.forEach(_merge);
    array2.forEach(_merge);

    var result = [];

    for(var key in keyedResult) if(keyedResult.hasOwnProperty(key)) {
        result.push(keyedResult[key]);
    }

    return result.sort(function(a, b) {
        return a.key - b.key;
    });
}

You could eliminate the sort if you don't care about the order. Another option is to use an array instead of the map I have used (keyedResult) if you have numeric keys and don't care about the array being sparse (i.e., if the keys are non-consecutive numbers). Here the key would also be the index of the array.

This solution also runs in O(n).

fiddle

like image 45
Vivin Paliath Avatar answered Sep 29 '22 13:09

Vivin Paliath