基于一个键合并两个对象数组

144

我有两个数组:

数组 1:

[
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
]

和第二个数组:

[
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
]

我需要根据 id 将这两个数组合并,并得到以下结果:

[
  { id: "abdc4051", date: "2017-01-24", name: "ab" },
  { id: "abdc4052", date: "2017-01-22", name: "abc" }
]

如何在不使用迭代Object.keys的情况下完成这个操作?


1
数组是否总是排序并且对于相同的“id”具有相同的索引? - Nina Scholz
3
以下是我的翻译:我会这样做:array1.map(x => { return array2.map(y => { if (y.id === x.id) { x.date = y.date; return x; } }); });注意:我尽量保持原文意思不变,同时使翻译更通俗易懂。 - Thadeus Ajayi
@Thadeus Ajayi - 这种方法比已被标记的答案提供的方法更为合适。只需按照以下方式填充缺失的大括号即可:array1.map((x) => array2.map((y) => { if (y.id === x.id) { x.date = y.date; return x; } }) ); - Lijo John
@ThadeusAjayi,你能解释一下为什么要使用x.date = y.date吗?这有什么作用?我不太了解Array.map。 - Jknight
1
@Jknight 我猜应该是 x.name = y.name,因为这是需要更新的字段。 - Thadeus Ajayi
24个回答

131

你可以像这样做 -

let arr1 = [
    { id: "abdc4051", date: "2017-01-24" },
    { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
    { id: "abdc4051", name: "ab" },
    { id: "abdc4052", name: "abc" }
];

let arr3 = arr1.map((item, i) => Object.assign({}, item, arr2[i]));

console.log(arr3);

如果arr1arr2的顺序不同,请使用以下代码:


let arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
];

let merged = [];

for(let i=0; i<arr1.length; i++) {
  merged.push({
   ...arr1[i], 
   ...(arr2.find((itmInner) => itmInner.id === arr1[i].id))}
  );
}

console.log(merged);

如果arr1arr2的顺序相同,请使用此方法

let arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
];

let merged = [];

for(let i=0; i<arr1.length; i++) {
  merged.push({
   ...arr1[i], 
   ...arr2[i]
  });
}

console.log(merged);


18
这只是合并数组吗?它不会像 OP 所要求的那样在 arr1.id == arr2.id 上进行“连接(join)”操作。 - Marty131
4
标题是“根据键合并两个对象数组”。OP在帖子中还提到“基于id”。 - Marty131
3
这不尊重关键字/键值对,它只是合并数组中的每一项。问题是:如何按关键字合并两个数组。对于arr1,您需要通过关键字“id”从arr2中找到正确的项。 - Domske
1
@Dominik 根据 OP 的要求更新了答案。 - Rajaprabhu Aravindasamy
2
有一件事要小心,两个数组必须具有完全相同的数据和键数。如果其中一个数组有2个键,另一个数组有3个键,它是无法正常工作的。 - Daisy
显示剩余4条评论

75
你可以用一行代码完成这个任务。
let arr1 = [
    { id: "abdc4051", date: "2017-01-24" },
    { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
    { id: "abdc4051", name: "ab" },
    { id: "abdc4052", name: "abc" }
];

const mergeById = (a1, a2) =>
    a1.map(itm => ({
        ...a2.find((item) => (item.id === itm.id) && item),
        ...itm
    }));

console.log(mergeById(arr1, arr2));

  1. 对数组1进行映射
  2. 在数组2中搜索数组1.id
  3. 如果找到...将数组2的结果展开并合并到数组1中

最终的数组将仅包含两个数组中匹配的id


4
“&& item” 在 find 方法中的目的是什么? - Fabrice
2
@Fabrice 我猜,在回答时,(错误的)假设是[].find()需要返回找到的项,而不仅仅是一个布尔值。 但既然现在已经在答案中了,我们可以为它编写一些用途 :-) 现在如果item是falsey,则避免匹配。所以它有点像三值关系代数(如SQL)中的JOIN(不会在NULL上进行等值连接)。换句话说,如果任一方的id缺失或falsey,则没有匹配。 - Robert Monfera
2
你不需要在这里使用 && itemfind 方法会返回找到的元素。 ...a2.find(item => item.id === itm.id), - mkupiniak
1
如果没有项目,那么谓词回调函数永远不会被调用,那为什么要它呢? - Cameron Wilby

72

即使合并的数组大小不同,该解决方案也适用。此外,即使匹配的键具有不同的名称。

使用Map将两个数组合并,如下所示:

const arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" },
  { id: "abdc4053", date: "2017-01-22" }
];
const arr2 = [
  { nameId: "abdc4051", name: "ab" },
  { nameId: "abdc4052", name: "abc" }
];

const map = new Map();
arr1.forEach(item => map.set(item.id, item));
arr2.forEach(item => map.set(item.nameId, {...map.get(item.nameId), ...item}));
const mergedArr = Array.from(map.values());

console.log(JSON.stringify(mergedArr));
.as-console-wrapper { max-height: 100% !important; top: 0; }

运行代码段以查看结果:

[
  {
    "id": "abdc4051",
    "date": "2017-01-24",
    "nameId": "abdc4051",
    "name": "ab"
  },
  {
    "id": "abdc4052",
    "date": "2017-01-22",
    "nameId": "abdc4052",
    "name": "abc"
  },
  {
    "id": "abdc4053",
    "date": "2017-01-22"
  }
]

10
这个答案比被接受的答案更好,因为它允许使用不同的键和大小不同的数组。 - Beerswiller
这也解决了我的问题,因为我必须在一个属性上进行组合,同时仍然返回没有组合的对象。 - Ronald91
这是截至2022年2月的最新答案。@Adel / Op 应该真正考虑更改已接受的答案。 - monsto
2
非常好,这个答案的时间复杂度是O(n),而如果我们使用mapfind或其他组合,它将是O(n^2)。非常感谢,我完全忘记了在这个问题中使用Map。 - heethjain21
我想要补充一下,它的作用类似于右外连接。假设你在两个对象中都有一个名为status的属性,arr1的status为1,而arr2的status为0,那么arr2的status将会出现在最终结果中。 - Sahil Kashyap

12

这里是一个使用 reduce 和 Object.assign 的 O(n) 解决方案

const joinById = ( ...lists ) =>
    Object.values(
        lists.reduce(
            ( idx, list ) => {
                list.forEach( ( record ) => {
                    if( idx[ record.id ] )
                        idx[ record.id ] = Object.assign( idx[ record.id ], record)
                    else
                        idx[ record.id ] = record
                } )
                return idx
            },
            {}
        )
    )

要在 OP 的情况下使用此函数,请将您想要拼接的数组传递给 joinById(请注意,lists 是一个 rest 参数)。

let joined = joinById(list1, list2)
每个列表都被缩减为一个单一的对象,其中键是ID,而值是对象。如果给定的键已经有一个值,它将调用object.assign进行合并当前记录。
这里是通用的O(n*m)解决方案,其中n是记录数,m是键数。这仅适用于有效的对象键。如果需要,您可以将任何值转换为base64并使用它。
const join = ( keys, ...lists ) =>
    lists.reduce(
        ( res, list ) => {
            list.forEach( ( record ) => {
                let hasNode = keys.reduce(
                    ( idx, key ) => idx && idx[ record[ key ] ],
                    res[ 0 ].tree
                )
                if( hasNode ) {
                    const i = hasNode.i
                    Object.assign( res[ i ].value, record )
                    res[ i ].found++
                } else {
                    let node = keys.reduce( ( idx, key ) => {
                        if( idx[ record[ key ] ] )
                            return idx[ record[ key ] ]
                        else
                            idx[ record[ key ] ] = {}
                        return idx[ record[ key ] ]
                    }, res[ 0 ].tree )
                    node.i = res[ 0 ].i++
                    res[ node.i ] = {
                        found: 1,
                        value: record
                    }
                }
            } )
            return res
        },
        [ { i: 1, tree: {} } ]
         )
         .slice( 1 )
         .filter( node => node.found === lists.length )
         .map( n => n.value )

这与joinById方法本质上相同,不同之处在于它保留了一个索引对象以标识要连接的记录。记录存储在数组中,索引存储给定关键字集的记录位置和它所在列表的数量。

每次遇到相同的关键字集时,它会查找树中的节点,更新其索引处的元素,并增加已找到的次数。

连接后,使用slice从数组中删除idx对象并删除每个集合中未找到的任何元素。这使它成为内部连接,您可以删除此过滤器并进行完全外部连接。

最后,将每个元素映射到其值,并得到了连接的数组。


1
这是我所偏爱的答案。非常感谢对每个提议解决方案进行详细分析。 - Vimalraj Selvam
抱歉,我无法理解这个答案。首先,我应该在哪里插入原问题的两个示例数组? - Henke
@Henke 对不起,我没有解释清楚。这两个数组被传递到第一个函数中。您可以复制并粘贴它,将这两个数组传递进去,然后返回连接的结果。我会使用OP的数据更新答案并提供示例。 - Snowbldr

11

你可以使用任意数量的数组,并在同一索引上映射新对象。

var array1 = [{ id: "abdc4051", date: "2017-01-24" }, { id: "abdc4052", date: "2017-01-22" }],
    array2 = [{ id: "abdc4051", name: "ab" }, { id: "abdc4052", name: "abc" }],
    result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i])));
    
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }


你能帮我明白这行代码吗?result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i]))); 它在干嘛?是比较两个数组并把得到的相同键值对赋值吗? - Lokesh Pandey
它将所有数组用于连接,并将a的分配单个元素(整个数组)的结果映射为后续的c作为项目与b和项目b[i] - Nina Scholz
5
这段代码在id不同时或顺序不正确时无法正常工作。var array1 = [{ id: "abdc4053", date: "2017-01-24" }, { id: "abdc4054", date: "2017-01-22" }], array2 = [{ id: "abdc4051", name: "ab" }, { id: "abdc4052", name: "abc" }], result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i]))); console.log(result); - OHM

10
如果你有两个数组需要根据值合并,即使它们的顺序不同。
let arr1 = [
    { id:"1", value:"this", other: "that" },
    { id:"2", value:"this", other: "that" }
];

let arr2 = [
    { id:"2", key:"val2"},
    { id:"1", key:"val1"}
];

您可以这样做

const result = arr1.map(item => {
    const obj = arr2.find(o => o.id === item.id);
    return { ...item, ...obj };
  });

console.log(result);

9

假设这两个数组长度相等,要按照 id 合并这两个数组:

arr1.map(item => ({
    ...item,
    ...arr2.find(({ id }) => id === item.id),
}));

8
我们可以在这里使用lodash。 _.merge的工作方式符合你的预期。它可以处理常见键值。
_.merge(array1, array2)

4

我遍历了第一个数组,并在第二个数组上使用.find方法来查找其中id相等的项,并返回结果。

const a = [{ id: "abdc4051", date: "2017-01-24" },{ id: "abdc4052", date: "2017-01-22" }];
const b = [{ id: "abdc4051", name: "ab" },{ id: "abdc4052", name: "abc" }];

console.log(a.map(itm => ({...itm, ...b.find(elm => elm.id == itm.id)})));


4

You can use array methods

let arrayA=[
{id: "abdc4051", date: "2017-01-24"},
{id: "abdc4052", date: "2017-01-22"}]

let arrayB=[
{id: "abdc4051", name: "ab"},
{id: "abdc4052", name: "abc"}]

let arrayC = [];


  

arrayA.forEach(function(element){
  arrayC.push({
  id:element.id,
  date:element.date,
  name:(arrayB.find(e=>e.id===element.id)).name
  });  
});

console.log(arrayC);

//0:{id: "abdc4051", date: "2017-01-24", name: "ab"}
//1:{id: "abdc4052", date: "2017-01-22", name: "abc"}


函数 isBiggerThan10() 只是一个剩余的代码吗? 在这里没有任何意义? - Henke

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