使用 Lodash 按属性合并对象数组

63

我有两个对象数组,它们代表具有标签和值的电子邮件地址:

var original = [
  {
    label: 'private',
    value: 'private@johndoe.com'
  },
  {
    label: 'work',
    value: 'work@johndoe.com'
  }
];

var update = [
  {
    label: 'private',
    value: 'me@johndoe.com'
  },
  {
    label: 'school',
    value: 'schhol@johndoe.com'
  }
];

现在我想通过 label 字段比较和合并这两个数组,以便结果看起来像这样:

var result = [
  {
    label: 'private',
    value: 'me@johndoe.com'
  },
  {
    label: 'work',
    value: 'work@johndoe.com'
  },
  {
    label: 'school',
    value: 'schol@johndoe.com'
  }
]

我该如何做到这一点,例如使用lodash


可能是重复问题:如何在JavaScript中合并两个数组并去重项 - Mario Santini
这并不是因为比较的元素是对象还是字符串会对可用的内容产生很大的影响。 - Yehuda Makarov
7个回答

120

_.unionBy()方法类似于 _.union(),但它接受一个迭代器函数,该函数会对每个数组的每个元素进行调用以生成计算唯一性所需的标准。结果值是从第一个出现该值的数组中选择的。

var original = [
  { label: 'private', value: 'private@johndoe.com' },
  { label: 'work', value: 'work@johndoe.com' }
];

var update = [
  { label: 'private', value: 'me@johndoe.com' },
  { label: 'school', value: 'schol@johndoe.com' }
];

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>


3
非常干净。 - jonathanGB
5
追加问题:当数组中的对象包含另一个对象作为值时,我该如何实现相同的结果?例如:[{ label: 'private', value: {propa: 'some text', propb: 12}}],我希望合并结果,以便如果标签相同,则原始值的属性与新值的属性合并。 - benjiman
4
如果两个数组中的属性名不同怎么办? - Mengo
如果我有20个或更多的数组怎么办?我找不到一种方法在循环中动态地将这些数组作为参数传递。有什么想法吗? - Suren

4
将列表转换为以

3
使用 _.unionBy() 的解决方案似乎更加简洁,但是它的表现完美。谢谢 :-) - benjiman
1
是的,当我看到它时,我几乎感动得哭了。 如此简单,感觉像lodash为所有事情都提供了功能 :) - Tamas Hegedus
没错,有时候你只是失去了整体的视野。;-) - benjiman

3
也许有点晚了,但我看到的所有解决方案都没有正确地合并两个数组,它们使用其中一个数组来循环,第二个数组中多余的元素不会被添加(假设这是所需的)。
正确的方法是对两个数组进行排序,并在两个数组内前进,合并匹配的元素,并从两个数组中添加缺少的元素。请查看下面的完整解决方案。这也需要O(n+m),这是您可以获得的最佳结果(不考虑排序本身的计算成本)。在我的代码中,我已经从数据库中获取了排序后的数据。

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>

结果如下:

[ { 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 } ]

2

我知道这不是您要求的内容,但以防有人偶然来到这个页面,这里是在Ramda中执行此操作的方法:

var original = [
  { label: 'private', value: 'private@johndoe.com' },
  { label: 'work', value: 'work@johndoe.com' }
];

var updated = [
  { label: 'private', value: 'me@johndoe.com' },
  { label: 'school', value: 'schol@johndoe.com' }
];

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

2

如果您使用的是lodash 3.x,其中没有_.unionBy(),则可以结合_.union()_.uniq()来获得相同的结果。

var original = [
  { label: 'private', value: 'private@johndoe.com' },
  { label: 'work', value: 'work@johndoe.com' }
];

var update = [
  { label: 'private', value: 'me@johndoe.com' },
  { label: 'school', value: 'schol@johndoe.com' }
];

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

console.log(result);

0

这里是使用 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>


0
也许有点晚了,但我看到的所有解决方案都没有正确地合并两个数组,它们使用其中一个数组进行循环,第二个数组中的任何多余元素都不会被添加(假设这是所需的)。
我也有同样的观察,所以自己编写了一些代码。这对我的用例有效,即如果“label”字段的值匹配,则合并每个对象:
const dataSetHashes = dataSets.map(dataSet => _.keyBy(dataSet, 'label'))
const resultHash = _.merge(
  {},
  ...dataSetLookups
)
const result = Object.values(resultLookup)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接