razorsyntax
razorsyntax

Reputation: 359

JavaScript - Combine and Add Values in Multiple Same Length Object Arrays

I'm looking to reduce an array of objects by matching identical date strings and add the totals together of those matching dates and combine them into a single object. I could have an array several thousand items long so I'm trying to increase (where ever possible) and complexity.

// before
let objArr = [
    {
        date: '01/01/2018',
        total: 1
    },
    {
        date: '01/01/2018',
        total: 2
    },
    {
        date: '01/02/2018',
        total: 3
    },
    {
        date: '01/02/2018',
        total: 4
    },
    ...
]

// final result
let finalArr = [
    {
        date: '01/01/2018',
        total: 3
    },
    {
        date: '01/02/2018',
        total: 7
    },
    ...
]

I couldn't seem to get the hang of reducing them using reduce:

objArr.reduce((acc, obj) => {
      acc.set(obj.date, (acc.get([obj.date]) || 0) + obj.total);
      return acc;
    }, new Map())

The result always ends up with the wrong totals or the last several array objects look like:

// bad output
badArray = [
    ...,
    {
        date: '01/02/2018',
        total: 4
    },
    {
        date: undefined,
        total: NaN
    },
    {
        date: undefined,
        total: NaN
    }
]

I wrote a script to check to ensure all the values in the date and total properties existed the way they needed to but I still end up with a bad array. The assumption here is my reduce function isn't right.

Upvotes: 2

Views: 367

Answers (2)

corecase
corecase

Reputation: 1298

I think Mark Meyer's answer is good, but this is also another option that uses an empty array as the initial value instead of a Map (maintaining the original structure of your input throughout):

 let objArr = [{date: '01/01/2018',total: 1},{date: '01/01/2018',total: 2},{date: '01/02/2018',total: 3},{date: '01/02/2018',total: 4},];
    
 const result = objArr.reduce((acc, obj) => {
     const existingObj = acc.find((o) => o.date === obj.date);
     if (existingObj) {
       existingObj.total += obj.total;
     } else {
       acc.push(obj);
     }
     return acc;
 }, []);
    
 console.log(result);

Note that the benefit of using an empty array as your initial value is that it maintains the original structure of your input (so you wouldn't have to iterate through the generated Map afterwards and convert it into an array of objects). :)

Upvotes: 0

Mark
Mark

Reputation: 92460

Your code is almost right. The problem is that you are setting the Map key to the date but trying to get the item by passing an array with the date rather than the date alone.

You are using: acc.get([obj.date]), when you probably want: acc.get(obj.date) (without the [ ])

let objArr = [{date: '01/01/2018',total: 1},{date: '01/01/2018',total: 2},{date: '01/02/2018',total: 3},{date: '01/02/2018',total: 4},]

let s = objArr.reduce((acc, obj) => {
    // not acc.get([obj.date]) !
    acc.set(obj.date, (acc.get(obj.date) || 0) + obj.total);
    return acc;
  }, new Map())

// turn the Map into something that will display:
console.log([...s.entries()].map(([date, total]) => ({date, total})))

Upvotes: 3

Related Questions