JavaScript通过ID合并对象

81

在Javascript中合并两个数组的正确方式是什么?

我有两个数组(例如):

var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]

我希望最终能够得到类似以下的结果:

var a3 = [{ id : 1, name : "test", count : "1"}, 
          { id : 2, name : "test2", count : "2"}]

根据'id'字段合并这两个数组,并且只是简单地添加额外的数据。

我尝试使用_.union来实现这个功能,但它只是将第二个数组中的值覆盖了第一个数组。


1
所以你实际想要做的是合并这些对象。 - JJJ
你的语法无效。你有一个合法的例子吗? - Mark Reed
请参见http://stackoverflow.com/questions/10688569/javascript-extend-array。 - megawac
与 https://dev59.com/OFoU5IYBdhLWcg3w1pX2 密切相关。 - Julian
@dippas OP想要根据ID合并对象,而不仅仅是合并数组。而且那些答案中的去重在对象上也不起作用。 - gre_gor
18个回答

67

ES6短解

const a3 = a1.map(t1 => ({...t1, ...a2.find(t2 => t2.id === t1.id)}))

5
它不会合并仅在a2中找到的ID。 - Gabriel
2
@Gabriel 我认为要求是有两个列表共享ID但具有不同的属性。 - Andreas Tzionis
1
这在多种情况下都无法正常工作。 - shijin

46

这应该能起到作用:

var mergedList = _.map(a1, function(item){
    return _.extend(item, _.findWhere(a2, { id: item.id }));
});

这假设a1中第二个对象的id应该是2而不是"2"


12
如果a2数组中的项目数量多于a1,那么这个方法将不起作用。 - CAOakley
1
@CAOakley - 是的,它会生效。您为什么认为它不起作用? - Gruff Bunny
1
@4castle 嘿嘿,当然是 lodash 了... 还有人在用 underscore 吗? :P - Andrea Puddu
非常棒的答案,我已经使用过这个解决方案不止一次。非常快速且易于理解。 - David O'Regan
6
Lodash 已经弃用了 _.findWhere,推荐使用带有迭代器的 _.find,因此请改用 _.find(a2, { id: item.id }) - moeabdol
显示剩余4条评论

28
假设ID是字符串且顺序不重要,您可以:
  1. 创建一个哈希表。
  2. 迭代两个数组并将数据存储在哈希表中,由ID索引。如果已经有该ID的一些数据,请使用Object.assign进行更新(ES6,可以进行polyfill处理)。
  3. 获取哈希映射值的数组。
var hash = Object.create(null);
a1.concat(a2).forEach(function(obj) {
    hash[obj.id] = Object.assign(hash[obj.id] || {}, obj);
});
var a3 = Object.keys(hash).map(function(key) {
    return hash[key];
});

在ECMAScript6中,如果ID不一定是字符串,你可以使用Map
var hash = new Map();
a1.concat(a2).forEach(function(obj) {
    hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj))
});
var a3 = Array.from(hash.values());

1
这个解决方案的渐近复杂度(线性)比大多数其他解决方案(二次)更好。您可以使用Underscore(或Lodash)更简洁地编写它:a3 = _.map(_.groupBy(a1.concat(a2), 'id'), arr => _.extend.apply(null, arr));。类似问题的类似答案在这里:https://dev59.com/OFoU5IYBdhLWcg3w1pX2#70474201。 - Julian

17

ES6 简化了这个过程:

let merge = (obj1, obj2) => ({...obj1, ...obj2});
注意,重复的键将被合并,第二个对象的值将优先,第一个对象的重复值将被忽略
例如:
let obj1 = {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj1Val"};
let obj2 = {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj2Val"};

merge(obj1, obj2)
// {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj2Val", uniqueObj2Key: "uniqueKeyValueObj2"}
merge(obj2, obj1)
// {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj1Val", uniqueObj1Key: "uniqueKeyValueObj1"}

完整解决方案(使用Lodash而不是Underscore

var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]
var merge = (obj1, obj2) => ({...obj1, ...obj2});
_.zipWith(a1, a2, merge)
(2) [{…}, {…}]
   0: {id: 1, name: "test", count: "1"}
   1: {id: 2, name: "test2", count: "2"}

如果您有要合并的数组,则可以按照以下方式执行:

var arrayOfArraysToMerge = [a1, a2, a3, a4]; //a3 and a4 are arrays like a1 and a2 but with different properties and same IDs.
_.zipWith(...arrayOfArraysToMerge, merge)
(2) [{…}, {…}]
   0: {id: 1, name: "test", count: "1", extra1: "val1", extra2: 1}
   1: {id: 2, name: "test2", count: "2", extra1: "val2", extra2: 2}

4
这不是对问题的回答。请检查期望的结果并改进您的回答,或者将其删除。 - Stephan Bijzitter
@StephanBijzitter 我更新了答案,现在你可以检查一下 :) 无论如何,我只是给他提供了一个解决问题的工具,而不是完整的答案,这样他也可以学习一些而不是复制/粘贴。 - Alberto
如果这是一个答案,那么它就是一个答案,而不是发现答案的工具。让你的答案更好的原因是A. 不使用库来实现答案,B. 解释得非常好,以至于可以教会人们如何回答问题,而不是简单地复制和粘贴。 - Christian Matthew
2
我认为如果列表的顺序不同,这个方法就行不通。 - user3067860
1
正如@user3067860所指出的那样,这取决于列表是否按相同顺序排列。这并不是为了否定Alberto的答案,只是为了指出。 - greaterKing

12

reduce 版本。

var a3 = a1.concat(a2).reduce((acc, x) => {
    acc[x.id] = Object.assign(acc[x.id] || {}, x);
    return acc;
}, {});
_.values(a3);

我认为这在函数式编程语言中是常见的做法。


2
如果您不想使用lodash,一个简单的方法是使用Object.values或者Object.keys(a3).map(k => a3[k]) - Rajesh

5

已经有很多好的答案了,我只是想再添加一个,这是昨天我需要解决的一个真实问题。

我有一个包含用户ID的消息数组,还有一个包含用户姓名和其他详细信息的用户数组。以下是我如何将用户详细信息添加到消息中。

var messages = [{userId: 2, content: "Salam"}, {userId: 5, content: "Hello"},{userId: 4, content: "Moi"}];
var users = [{id: 2, name: "Grace"}, {id: 4, name: "Janetta"},{id: 5, name: "Sara"}];

var messagesWithUserNames = messages.map((msg)=> {
  var haveEqualId = (user) => user.id === msg.userId
  var userWithEqualId= users.find(haveEqualId)
  return Object.assign({}, msg, userWithEqualId)
})
console.log(messagesWithUserNames)

5

lodash的实现:

var merged = _.map(a1, function(item) {
    return _.assign(item, _.find(a2, ['id', item.id]));
});

结果如下:
[  
   {  
      "id":1,
      "name":"test",
      "count":"1"
   },
   {  
      "id":2,
      "name":"test2",
      "count":"2"
   }
]

3
如果a2数组中的项比a1数组多,那么这种方法将行不通。 - Anna

5

纯JS解决方案

const a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
const a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]

const merge = (arr1, arr2) => {
  const temp = []

  arr1.forEach(x => {
    arr2.forEach(y => {
      if (x.id === y.id) {
        temp.push({ ...x, ...y })
      }
    })
  })

  return temp
}

console.log(merge(a1, a2))

这个答案很好,除了它不能很好地处理一个数组为空的情况。它会返回一个空数组。 - nayiaw

4

我想补充一下@daisihi上面的答案,主要区别在于这个使用了展开运算符。此外,在结尾处,我删除了id,因为一开始它并不受欢迎。

const a3 = [...a1, ...a2].reduce((acc, x) => {
   acc[x.id] = {...acc[x.id] || {}, ...x};
   return acc;
}, {});

这部分内容源自另一篇文章。 从数组中的对象列表中删除属性

const newArray = Object.values(a3).map(({id, ...keepAttrs}) => keepAttrs);

3

发现其他解决方案在某些情况下失败,因此在这里编写更好的解决方案

const a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
const a2 = [{ id : 3, count : "3"}, { id : 1, count : "1"}, {id : 2, count : "2"}]


const mergeHelper = new Map(a1.map(x => [x.id, x]));

for (const x of a2) {
    if (mergeHelper.has(x.id)) {
        const item = mergeHelper.get(x.id);
        mergeHelper.set(x.id, {...item, ...x});
    } else {
        mergeHelper.set(x.id, x);
    }
}

const mergedList = [...mergeHelper.values()];
// For sorted array 
// const mergedSortedList = [...mergeHelper.values()].sort((a, b) => a.id - b.id);

console.log(mergedList)

使用 JavaScript 的 Map 对象比其他方法更快,在数组长度很大时非常有帮助。


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