-1

Here is the scenario I am looking at:

I want to reduce these objects

const data = [
  {
    id: 1,
    totalAmount: 1500,
    date: '2021-01-01',
    vendor_id: 2,
    totalTransaction: 12,
    isRefund: false,
  },
  {
    id: 2,
    totalAmount: 200,
    date: '2021-01-01',
    vendor_id: 2,
    totalTransaction: 2,
    isRefund: true,
  },
  {
    id: 3,
    totalAmount: 200,
    date: '2021-01-01',
    vendor_id: 2,
    totalTransaction: 1,
    isRefund: true,
  },
];

and I found a solution that adds their values:

const deepMergeSum = (obj1, obj2) => {
  return Object.keys(obj1).reduce((acc, key) => {
    if (typeof obj2[key] === 'object') {
      acc[key] = deepMergeSum(obj1[key], obj2[key]);
    } else if (obj2.hasOwnProperty(key) && !isNaN(parseFloat(obj2[key]))) {
      acc[key] = obj1[key] + obj2[key]
    }
    return acc;
  }, {});
};

const result = data.reduce((acc, obj) => (acc = deepMergeSum(acc, obj)));
const array = []
const newArray = [...array, result]

which results to:

const newArray = [
 {
   id: 6,
   totalAmount: 1900,
   date: '2021-01-012021-01-012021-01-01',
   vendor_id: 6,
   totalTransaction: 15
 }
]

And now my problem is I don't know yet how to work this around to have my expected output which if isRefund is true, it must be subtracted instead of being added, retain the vendor_id and also the concatenated date instead of only one entry date:

const newArray = [
 {
   id: 1(generate new id if possible),
   totalAmount: 1100,
   date: '2021-01-01',
   vendor_id: 2,
   totalTransaction: 15,
   isRefund: null(this can be removed if not applicable),
 },
];

I will accept and try to understand any better way or workaround for this. Thank you very much.

Shironekomaru
  • 361
  • 1
  • 6
  • 15
  • The easiest solution is to convert _"if isRefund is true"_ into an actual `if (...)` (isRefund is a `key`) and in that case subtract and in any other case (`else`) add the values -> What have you tried so far to solve this on your own? – Andreas Jan 22 '22 at 12:12
  • @Andreas I Tried adding your suggestion ealier like this: if (obj1["isRefund"] === true) { acc["totalAmount"] = obj1["totalAmount"] - obj2["totalAmount"] } else .... And it only returned: [ { totalAmount: 1900 } ] – Shironekomaru Jan 22 '22 at 12:16
  • @Andreas while using the obj2 instead of obj1 returned just the same as the result on the post since obj2 value is false. – Shironekomaru Jan 22 '22 at 12:18
  • `key` in _"(isRefund is a `key`)"_ is formatted as code for a reason ;) – Andreas Jan 22 '22 at 12:19
  • @Andreas I'm really sorry I don't quite get how you wanted to mean it. – Shironekomaru Jan 22 '22 at 12:23
  • Do all your entries always have the same date? What should happen when they are different? – trincot Jan 22 '22 at 12:25
  • @trincot Ah yes. There will be an initial filtering of date so these suppose set of data will always be having the same dates – Shironekomaru Jan 22 '22 at 12:26
  • You say that if `isRefund` is true, the amount should be subtracted, but then for the example input I would expect -1100, not 1300. Can you clarify how you got 1300? Are you sure you want to *add* `vendor_id` values? That looks weird... – trincot Jan 22 '22 at 12:39
  • @trincot Oh no, Im really sorry I messed up my example. Ive edited the data correctly. Ah no, that's the other field I forgot to put in my problem. I also wanted to retain the `vendor_id` – Shironekomaru Jan 22 '22 at 12:42
  • You haven't shown your expected output – pilchard Jan 22 '22 at 12:52
  • @pilchard the last code snippet is my expected output while the 2nd to the last was the current actual outpu for the solution I found. – Shironekomaru Jan 22 '22 at 12:53
  • I see, so just keep whichever `id` is encountered first and sum the amount and totalTransaction? This looks a lot like a basic `group by` grouped on vendorId [Most efficient method to groupby on an array of objects](https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects) – pilchard Jan 22 '22 at 12:54
  • @pilchard yes. It can be that way too if possible. – Shironekomaru Jan 22 '22 at 12:59
  • Does this answer your question? [Most efficient method to groupby on an array of objects](https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects) – pilchard Jan 22 '22 at 13:09
  • Ill try this too. Thank you very much for the huge help. – Shironekomaru Jan 22 '22 at 13:11

2 Answers2

1

As you want custom behaviour for several fields, and don't need the recursive aspect of the merge, I would suggest you create a custom merge function, specific to your business logic:

const data = [{id: 1,totalAmount: 1500,date: '2021-01-01',vendor_id: 2,totalTransaction: 12,isRefund: false,},{id: 2,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 2,isRefund: true,},{id: 3,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 1,isRefund: true,},];

function customMerge(a, b) {
    if (a.vendor_id !== b.vendor_id || a.date !== b.date) {
        throw "Both date and vendor_id must be the same";
    }
    return {
        id: Math.max(a.id, b.id),
        totalAmount: (a.isRefund ? -a.totalAmount : a.totalAmount) 
                   + (b.isRefund ? -b.totalAmount : b.totalAmount),
        date: a.date,
        vendor_id: a.vendor_id,
        totalTransaction: a.totalTransaction + b.totalTransaction
    };
}

const result = data.reduce(customMerge);
if (data.length > 1) result.id++; // Make id unique
console.log(result);

You could also reintroduce the isRefund property in the result for when the total amount turns out to be negative (only do this when data.length > 1 as otherwise result is just the original single object in data):

result.isRefund = result.totalAmount < 0;
result.totalAmount = Math.abs(result.totalAmount);

Distinct results for different dates and/or vendors

Then use a "dictionary" (plain object or Map) keyed by date/vendor combinations, each having an aggregating object as value.

To demonstrate, I added one more object in the data that has a different date and amount of 300:

const data = [{id: 1,totalAmount: 1500,date: '2021-01-01',vendor_id: 2,totalTransaction: 12,isRefund: false,},{id: 2,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 2,isRefund: true,},{id: 3,totalAmount: 200,date: '2021-01-01',vendor_id: 2,totalTransaction: 1,isRefund: true,},{id: 4,totalAmount: 300,date: '2021-01-02',vendor_id: 2,totalTransaction: 1,isRefund: false,}];

function customMerge(acc, {id, date, vendor_id, totalAmount, totalTransaction, isRefund}) {
    let key = date + "_" + vendor_id;
    if (!(id <= acc.id)) acc.id = id;
    acc[key] ??= {
        date,
        vendor_id,
        totalAmount: 0,
        totalTransaction: 0
    };
    acc[key].totalAmount += isRefund ? -totalAmount : totalAmount;
    acc[key].totalTransaction += totalTransaction;
    return acc;
}

let {id, ...grouped} = data.reduce(customMerge, {});
let result = Object.values(grouped).map(item => Object.assign(item, { id: ++id }));
console.log(result);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • thank you very much @trincot for the huge help. I'm really sorry but if I may ask, can this also be customized if there are multiple dates? since the filter was a ranged date picker. I answered your question earlier without even thinking of the other scenario – Shironekomaru Jan 22 '22 at 13:13
  • Sure it can, that's why asked about it in the first place. – trincot Jan 22 '22 at 13:14
  • 1
    Added a section to my answer about that. – trincot Jan 22 '22 at 13:35
  • Really. Thank you very very much for the huge help. Your 2nd solution was still beyond my current understanding but I will still try to understand it as I think it really will be helpful for my future encounter. – Shironekomaru Jan 22 '22 at 13:44
1

it could be help, if you are looking for the same output, can add other checklist based on your requirement, for filtered date, logic would be little different but the output will be same.

const getTotalTranscation = () =>
  transctionLists.reduce((prev, current) => {
    const totalAmount = current.isRefund
      ? prev.totalAmount - current.totalAmount
      : prev.totalAmount + current.totalAmount;

    const totalTransaction = current.isRefund
      ? prev.totalTransaction - current.totalTransaction
      : prev.totalTransaction + current.totalTransaction;

    return {
      ...current,
      id: current.id + 1,
      totalAmount,
      totalTransaction,
      isRefund: totalAmount < 0,
    };
  });