1

I plan to merge two objects:

var c = {
    name: "doo",
    arr: [
        {
            id: 1,
            ver: 1
        },
        {
            id: 3,
            ver: 3
        }
    ]
};


var b = {
    name: "moo",
    arr: [
        {
            id: 1,
            ver: 0
        },
        {
            id: 2,
            ver: 0
        }
    ]
};

When using Object.assign({},b,c) what happens is, that the b.arr is simply being replaced with c.arr.

My question is, how do I preserve objects inside the b.arr that are not in c.arr but still merge objects from that array when they match b.arr[0].id === c.arr[0].id. The desired outcome would look like:

{
    name: "doo",
    arr: [
        {
            id: 1,
            ver: 1
        },
        {
            id: 2,
            ver: 0
        },
        {
            id: 3,
            ver: 3
        }
    ]
}

Thanks.

4 Answers4

2

You could have a look at ArrayUtils.addAll() from the apache commons

bwright
  • 806
  • 10
  • 27
  • Oh Sorry. I missread the tag and was thinking about Java. In JavaScript You would have to merge the Arrays and then remove duplicates as posten in http://stackoverflow.com/questions/1584370/how-to-merge-two-arrays-in-javascript-and-de-duplicate-items – bwright Mar 24 '15 at 15:26
1

As soon as you use lodash - you may use a combination of lodash's functions. It may look a bit complex but it's not:

_.assign({}, b, c, function(objectValue, sourceValue, key, object, source) {
  //merging array - custom logic
  if (_.isArray(sourceValue)) {
    //if the property isn't set yet - copy sourceValue
    if (typeof objectValue == 'undefined') {
      return sourceValue.slice();
    } else if (_.isArray(objectValue)) {
      //if array already exists - merge 2 arrays
      _.forEach(sourceValue, function(sourceArrayItem) {
        //find object with the same ID's
        var objectArrayItem = _.find(objectValue, {id: sourceArrayItem.id});
        if (objectArrayItem) {
          //merge objects
          _.assign(objectArrayItem, sourceArrayItem);
        } else {
          objectValue.push(sourceArrayItem);
        }
      });
      return objectValue;
    }
  }
  //if sourceValue isn't array - simply use it
  return sourceValue;
});

See the full demo here.

Kiril
  • 2,827
  • 1
  • 33
  • 41
  • Thanks. I think I will parametrize the inner function so that you can pass in the key to merge against (so that one can reuse the function for other contexts as well) –  Mar 24 '15 at 18:05
  • Sure, it's up to you :) – Kiril Mar 24 '15 at 18:44
0

Try this function:

function mergeArrayObjects (a, b) {
    var tmp, // Temporary array that will be returned
        // Cache values
        i = 0,
        max = 0;

    // Check if a is an array
    if ( typeof a !== 'object' || typeof a.indexOf === 'undefined')
        return false;
    // Check if b is an array
    if ( typeof b !== 'object' || typeof b.indexOf === 'undefined')
        return false;

    // Populate tmp with a
    tmp = a;

    // For each item in b, check if a already has it. If not, add it.
    for (i = 0, max = b.length; i < max; i++) {
        if (tmp.indexOf(b[i]) === -1)
            tmp.push(b[i]);
    }
    // Return the array
    return tmp;
}

JsFiddle here

Note: Because I'm anal, I decided to see if this function is faster than the alternative proposed. It is.

Martin
  • 5,236
  • 4
  • 35
  • 60
  • Thanks. I got the same results as you with lodash: var merge = _.merge(def,empty, function(a, b) { if (_.isArray(a)) { return a.concat(b); } }); The thing is that the resulting array needs to merge objects with same ID. –  Mar 24 '15 at 15:48
  • Well that's not really what you are originally asking. It's not a merge in this case, as there are certain objects being overwritten (`b.arr[1]` in your example gets overwritten by `c.arr[1]`) – Martin Mar 24 '15 at 16:16
  • Well, it is a partial merge what I am after: only override if the objects share the same id -> b.arr[0].id === c.arr[0].id, else preserve the other object. –  Mar 24 '15 at 16:20
  • I'm guessing you want the higher `ver` to take precedence? – Martin Mar 24 '15 at 16:21
  • Well, I want the object c that I merge into object b take precendence and hence those properties should override the properties of object b. Similar to Object.assign(), _.merge, $.extend. That is all clear to me. And that works. The caveat is that when it comes to arrays with objects in them, they get replaced by index b[1] = c[1] etc. –  Mar 24 '15 at 16:34
  • Okay, so if there are two items with the same IDs, you want the second param's array's matching `id` object to overwrite what was there before? Regardless of other values – Martin Mar 24 '15 at 16:36
  • Yes, basically I want the objects inside the arrays to merge rather than just replace them by array index. One trivial solution to this would be to avoid the arrays that hold the objects but use an object and use keys for the objects: arr : { "#1":{id:1,ver:1} } … I could simply use Object.assign() for them to merge then. –  Mar 24 '15 at 16:41
  • 1
    I was going to suggest that, but figured for the point of answering your question as is, I will write you a function so you don't have to modify existing data – Martin Mar 24 '15 at 16:44
0

Using lodash, I would do something like this:

var first = {
    name: 'doo',
    arr: [
        { id: 1, ver: 1 },
        { id: 3, ver: 3 }
    ]
};

var second = {
    name: 'moo',
    arr: [
        { id: 1, ver: 0 },
        { id: 2, ver: 0 }
    ]
};

_.merge(first, second, function(a, b) { 
    if (_.isArray(a)) {
        return _.uniq(_.union(a, b), 'id');
    } else {
        return a;
    }
});

// →
// {
//   name: 'doo',
//   arr: [
//     { id: 1, ver: 1 },
//     { id: 2, ver: 0 },
//     { id: 3, ver: 3 }
//   ]
// }

The merge() function let's you specify a customizer callback for things like arrays. So we just need to check it it's an array we're dealing with, and if so, use the uniq() and union() functions to find the unique values by the id property.

Adam Boduch
  • 9,895
  • 3
  • 23
  • 38