網站搜尋

javascript - 根據鍵合併兩個物件陣列


我有兩個陣列:

陣列 1:

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

以及陣列 2:

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

你可以這樣做 -

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);

如果 arr1 和 arr2 的順序不同,請使用以下程式碼:

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);

如果 arr1 和 arr2 順序相同,請使用此方法

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);

答案#2

這個解決方案即使在合併的陣列大小不同時也適用。此外,即使匹配的鍵名稱不同,也適用。

使用 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"
  }
]

答案#3

你可以在一行中完成這個

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. 對 array1 進行映射
  2. 在 array2 中搜尋 array1.id
  3. 如果你找到它……將 array2 的結果展開到 array1 中

最終的陣列將只包含兩個陣列中匹配的 id

答案#4

這裡有一個使用 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
            },
            {}
        )
    )

要在原作者的案例中使用此功能,將你想要合併的陣列傳入 joinById(注意 lists 是一個剩餘參數)。

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 方法相同,除了它保留一個索引對象來識別要連接的記錄。這些記錄被存儲在數組中,索引存儲了給定鍵集的記錄位置以及它被發現的列表數量。

每次遇到相同的鍵集合時,它會在樹中找到該節點,更新其索引處的元素,並增加其被找到的次數。

加入後,idx 物件會使用 slice 從陣列中移除,並且任何在每個集合中未找到的元素也會被移除。這使它成為內部連接,你可以移除這個過濾器來取得完整外部連接。

最後,每個元素都映射到它的值,然後你就得到了合併的陣列。

答案#5

你可以使用任意數量的陣列,並在相同的索引上映射新的物件。

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; }

答案#6

如果你有兩個陣列需要根據值合併,即使它們的順序不同

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);

答案#7

假設兩個陣列長度相等,要在 id 上合併這兩個陣列:

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