Search code examples
javascriptarraysjavascript-objects

Merge 2 objects of same array and total the common values within the array inside each object


I tried looking into it but couldn't find the specific use case for my scenario. I am sure it is fairly simple but I am stuck on this for days. Any help will be appreciated

const stores = [
{
  id: "61f27aeb766e4b2924532f98",
  xName: 'SaurabhTest2',
  merchantIDs: [ "61f27bca766e4b2924532fb3" ],
  totalMerAcc: 1,
  oneTimeData: 100
},
{
  id: "61f2769b766e4b2924532f1f",
  xName: 'SaurabhTest',
  merchantIDs: [ "61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31" ],
  totalMerAcc: 2,
  oneTimeData: 100
},
{
  id: "61f2769b766e4b2924532f1f",
  xName: 'SaurabhTest',
  merchantIDs: [ "61f277b8766e4b2924532f31" ],
  totalMerAcc: 1,
  oneTimeData: 100
}]

Desired output:

[
{
  id: "61f27aeb766e4b2924532f98",
  xName: 'SaurabhTest2',
  merchantIDs: [ "61f27bca766e4b2924532fb3" ],
  totalMerAcc: 1,
  oneTimeData: 100
},
{
  id: "61f2769b766e4b2924532f1f",
  xName: 'SaurabhTest',
  merchantIDs: [ "61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31" ],
  totalMerAcc: 2,
  oneTimeData: 100
}]

First the stores should merged based on "id" but then also check if the merchantsIDs already exists between the 2 mergers (there could be more than 2 same storeIDs here so more objects), and include all the distinct ones in the merchantIDs within an object and then total it in "totalMerAcc" as well to return something like above.

Here's the code I have written this this point:

function mergeRecurrentsStores(businessStores) {
const result = businessStores.map((item) => {
    return [
        item[0],
        ...item[1]
            .reduce((accumulator, currentStore) => {
                const key = currentStore.id.toString();
                const innerItem =
                    accumulator.get(key) ||
                    Object.assign({}, currentStore, {
                        xName: currentStore.xName,
                        merchantIDs: currentStore.merchantIDs,
                        totalMerAcc: 0,
                        oneTimeData: currentStore.oneTimeData,
                    });
                if(innerItem.merchantIDs.some(i => i.includes(currentStore.merchantIDs)) {
                    
                }
                innerItem.totalMerAcc += currentStore.totalMerAcc;
                return accumulator.set(key, innerItem);
            }, new Map())
            .values(),
    ];
});
return result;}

The example structure is inside item[1] which I am reducing. You can neglect the item[0] case. That's just for next thing in pipeline.

Any help is welcomed.


Solution

  • Array's map implements a mapping operation. Mapping operations produce a 1:1 result, one result item for each input item. But you don't want that, so map isn't the right tool.

    If you were doing functional programming with predefined, reusable reducer functions, reduce might be the right tool, but if you aren't (and you don't seem to be, your reducer is inline), it's just an over-complicated loop. Instead, let's do a nice simple loop.

    const byId = new Map();
    for (const store of stores) {
        let previous = byId.get(store.id);
        if (!previous) {
            // New one
            byId.set(store.id, store);
        } else {
            // Merge with previous
            // NOTE: If you don't want to modify the object in place, change
            // `const previous` above to `let previous` and uncomment this:
            /*
            previous = {...previous, merchantIDs: [...previous.merchantIDs]};
            byId.set(previous.id, previous);
            */
            for (const merchantID of store.merchantIDs) {
                if (!previous.merchantIDs.includes(merchantID)) {
                    previous.merchantIDs.push(merchantID);
                }
            }
            previous.totalMerAcc = previous.merchantIDs.length; // It seems unnecessary to have `totalMerAcc`
        }
    }
    
    const result = [...byId.values()];
    

    Live Example:

    const stores = [
        {
            id: "61f27aeb766e4b2924532f98",
            xName: 'SaurabhTest2',
            merchantIDs: ["61f27bca766e4b2924532fb3"],
            totalMerAcc: 1,
            oneTimeData: 100
        },
        {
            id: "61f2769b766e4b2924532f1f",
            xName: 'SaurabhTest',
            merchantIDs: ["61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31"],
            totalMerAcc: 2,
            oneTimeData: 100
        },
        {
            id: "61f2769b766e4b2924532f1f",
            xName: 'SaurabhTest',
            merchantIDs: ["61f277b8766e4b2924532f31"],
            totalMerAcc: 1,
            oneTimeData: 100
        }
    ];
    
    const byId = new Map();
    for (const store of stores) {
        const previous = byId.get(store.id);
        if (!previous) {
            // New one
            byId.set(store.id, store);
        } else {
            // Merge with previous
            // NOTE: If you don't want to modify the object in place, change
            // `const previous` above to `let previous` and uncomment this:
            /*
            previous = {...previous, merchantIDs: [...previous.merchantIDs]};
            byId.set(previous.id, previous);
            */
            for (const merchantID of store.merchantIDs) {
                if (!previous.merchantIDs.includes(merchantID)) {
                    previous.merchantIDs.push(merchantID);
                }
            }
            previous.totalMerAcc = previous.merchantIDs.length; // It seems unnecessary to have `totalMerAcc`
        }
    }
    
    const result = [...byId.values()];
    console.log(JSON.stringify(result, null, 4));
    .as-console-wrapper {
        max-height: 100% !important;
    }

    For completeness, though, we can do it with reduce (because reduce is a Swiss-army knife, you can do anything related to arrays with it, because again it's basically a loop):

    const result = [...stores.reduce((byId, store) => {
        const previous = byId.get(store.id);
        if (!previous) {
            return byId.set(store.id, store);
        }
        const merchantIDs = [...new Set([...previous.merchantIDs, ...store.merchantIDs])];
        return byId.set(store.id, {
            ...previous,
            merchantIDs,
            totalMerAcc: merchantIDs.length,
        });
    }, new Map()).values()];
    

    Live Example:

    const stores = [
        {
            id: "61f27aeb766e4b2924532f98",
            xName: 'SaurabhTest2',
            merchantIDs: ["61f27bca766e4b2924532fb3"],
            totalMerAcc: 1,
            oneTimeData: 100
        },
        {
            id: "61f2769b766e4b2924532f1f",
            xName: 'SaurabhTest',
            merchantIDs: ["61f2788e766e4b2924532f54", "61f277b8766e4b2924532f31"],
            totalMerAcc: 2,
            oneTimeData: 100
        },
        {
            id: "61f2769b766e4b2924532f1f",
            xName: 'SaurabhTest',
            merchantIDs: ["61f277b8766e4b2924532f31"],
            totalMerAcc: 1,
            oneTimeData: 100
        }
    ];
    
    const result = [...stores.reduce((byId, store) => {
        const previous = byId.get(store.id);
        if (!previous) {
            return byId.set(store.id, store);
        }
        const merchantIDs = [...new Set([...previous.merchantIDs, ...store.merchantIDs])];
        return byId.set(store.id, {
            ...previous,
            merchantIDs,
            totalMerAcc: merchantIDs.length,
        });
    }, new Map()).values()];
    console.log(JSON.stringify(result, null, 4));
    .as-console-wrapper {
        max-height: 100% !important;
    }