Merge Array of Objects by Property using Lodash

JavascriptArraysMergeLodash

Javascript Problem Overview


I have two arrays of objects that represent email addresses that have a label and a value:

var original = [
  {
    label: 'private',
    value: '[email protected]'
  },
  {
    label: 'work',
    value: '[email protected]'
  }
];

var update = [
  {
    label: 'private',
    value: '[email protected]'
  },
  {
    label: 'school',
    value: '[email protected]'
  }
];

Now I want to compare and merge the two arrays by the label field, so that the result would look like this:

var result = [  {    label: 'private',    value: '[email protected]'  },  {    label: 'work',    value: '[email protected]'  },  {    label: 'school',    value: '[email protected]'  }]

How can I do this e.g. using lodash?

Javascript Solutions


Solution 1 - Javascript

_.unionBy():
This method is like _.union except that it accepts iteratee which is invoked for each element of each arrays to generate the criterion by which uniqueness is computed. Result values are chosen from the first array in which the value occurs.

var original = [
  { label: 'private', value: '[email protected]' },
  { label: 'work', value: '[email protected]' }
];

var update = [
  { label: 'private', value: '[email protected]' },
  { label: 'school', value: '[email protected]' }
];

var result = _.unionBy(update, original, "label");

console.log(result);

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>

Solution 2 - Javascript

Convert the lists to objects keyed by label, merge them by _.assign, and convert it back to an array. It will even retain order of the items on most browsers.

var original = [
  {
    label: 'private',
    value: '[email protected]'
  },
  {
    label: 'work',
    value: '[email protected]'
  }
];

var update = [
  {
    label: 'private',
    value: '[email protected]'
  },
  {
    label: 'school',
    value: '[email protected]'
  }
];

console.log(
  _.map(
    _.assign(
      _.mapKeys(original, v => v.label),
      _.mapKeys(update, v => v.label)
    )
  )
);


// or remove more duplicated code using spread

console.log(
  _.map(
    _.assign(
      ...[original, update].map(
        coll => _.mapKeys(coll, v => v.label)
      )
    )
  )
);

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.js"></script>

Solution 3 - Javascript

Perhaps a bit late, but all the solutions I have seen don't join both arrays correctly, they use one of the arrays to loop on and any excess elements in the second array don't get added (assuming this is what is required).

The right way is to sort both arrays and move forward within both arrays, merging the matches elements and adding the missing elements from both arrays. Please find full solution below. This also takes O(n+m) which is the best you can get (without the computational costs for sort itself). In my code I already got the data sorted from the database.

function mergeObjectsBasedOnKey(array1, array2, compareFn, mergeFn, alreadySorted) {
  var array1Index = 0;
  var array2Index = 0;

  const merged = [];

  if (!alreadySorted) {
    array1.sort(compareFn);
    array2.sort(compareFn);
  }

  while (array1Index < array1.length && array2Index < array2.length) {
    var comparedValue = compareFn(array1[array1Index], array2[array2Index]);
    if (comparedValue === 0) {
      merged.push(mergeFn(array1[array1Index], array2[array2Index]));
      array1Index++;
      array2Index++;
    } else if (comparedValue < 0) {
      merged.push(mergeFn(array1[array1Index]));
      array1Index++;
    } else {
      merged.push(mergeFn(array2[array2Index]));
      array2Index++;
    }
  }
  while (array1Index < array1.length) {
    merged.push(mergeFn(array1[array1Index]));
    array1Index++;
  }
  while (array2Index < array2.length) {
    merged.push(mergeFn(array2[array2Index]));
    array2Index++;
  }
  return merged;
}


const array1 = [{
    "id": 10,
    isArray1: true
  },
  {
    "id": 11,
    isArray1: true
  },
  {
    "id": 12,
    isArray1: true
  },
];
const array2 = [{
    "id": 8,
    isArray2: true
  },
  {
    "id": 11,
    isArray2: true
  },
  {
    "id": 15,
    isArray2: true
  },
];

const result = mergeObjectsBasedOnKey(array1, array2, function(a, b) {
  return a.id - b.id;
}, function(a, b) {
  if (b) {
    return _.merge(a, b);
  }
  return _.merge(a, {
    isArray1: true,
    isArray2: true
  });
});

console.log(result);

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

And the results would be:

[ { id: 8, isArray2: true, isArray1: true },
  { id: 10, isArray1: true, isArray2: true },
  { id: 11, isArray1: true, isArray2: true },
  { id: 12, isArray1: true, isArray2: true },
  { id: 15, isArray2: true, isArray1: true } ]

Solution 4 - Javascript

I know it is not what asked for but just in case someone stumbled up on this page here is how you do this in ramda:

var original = [
  { label: 'private', value: '[email protected]' },
  { label: 'work', value: '[email protected]' }
];

var updated = [
  { label: 'private', value: '[email protected]' },
  { label: 'school', value: '[email protected]' }
];

unionWith(eqBy(prop('label')), updated, original);

Solution 5 - Javascript

In case you are using lodash 3.x where _.unionBy() was not there, you can combine _.union() and _.uniq() to get the same result.

var original = [
  { label: 'private', value: '[email protected]' },
  { label: 'work', value: '[email protected]' }
];

var update = [
  { label: 'private', value: '[email protected]' },
  { label: 'school', value: '[email protected]' }
];

var result = _.uniq(_.union(update, original), "label"); 

console.log(result);

Solution 6 - Javascript

Here is another way to merge two objects using Lodash:

let a = [{
    content: 'aaa',
    name: 'bbb2'
  },
  {
    content: 'aad',
    name: 'ccd'
  }
];

let b = [{
    content: 'aaa',
    name: 'bbb'
  },
  {
    content: 'aad1',
    name: 'ccd1'
  }
];

let c = [...a, ...b];

let d = _.uniq(c, function(data) {
  return data.content;
})

console.log(d);

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

Solution 7 - Javascript

> Perhaps a bit late, but all the solutions I have seen don't join both arrays correctly, they use one of the arrays to loop on and any excess elements in the second array don't get added (assuming this is what is required).

I had the same observation so put something together myself. This is working for my use case, which is to merge each object if the value of the 'label' field matches:

const dataSetHashes = dataSets.map(dataSet => _.keyBy(dataSet, 'label'))
const resultHash = _.merge(
  {},
  ...dataSetLookups
)
const result = Object.values(resultLookup)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbenjimanView Question on Stackoverflow
Solution 1 - JavascriptAndreasView Answer on Stackoverflow
Solution 2 - JavascriptTamas HegedusView Answer on Stackoverflow
Solution 3 - JavascriptRalphView Answer on Stackoverflow
Solution 4 - JavascriptRevanth KumarView Answer on Stackoverflow
Solution 5 - JavascriptManish KumarView Answer on Stackoverflow
Solution 6 - Javascriptjain77View Answer on Stackoverflow
Solution 7 - JavascriptzayquanView Answer on Stackoverflow