Search code examples
javascriptreduce

Total JavaScript Array by one Property


I've looked at lots of examples but cannot get this to work. At best, I end up with a version of the existing array with properties replicated that should be lost in the summary.

I would like to output a new array of totals for each location and not include any details for specific periods, the period property etc. I think my issue is with Object.assign({}, o); where I am creating a full copy, but I can't see how to just create an object with just the location, to which the totals can be added.

This is a simplified example and in the original I am summing multiple columns and dropping multiple columns.

let helper = {};
let locationPeriodCounts = [
  {"period": "2023-10-21", "location": "228", "countIn": 6, "countOut": 1},
  {"period": "2023-10-22", "location": "228", "countIn": 8, "countOut": 2},
  {"period": "2023-10-23", "location": "228", "countIn": 3, "countOut": 3},
  {"period": "2023-10-24", "location": "228", "countIn": 1, "countOut": 4},
  {"period": "2023-10-21", "location": "229", "countIn": 5, "countOut": 6},
  {"period": "2023-10-22", "location": "229", "countIn": 18, "countOut": 6},
  {"period": "2023-10-23", "location": "229", "countIn": 8, "countOut": 6},
  {"period": "2023-10-24", "location": "230", "countIn": 3, "countOut": 6},
  {"period": "2023-10-25", "location": "230", "countIn": 4, "countOut": 6}
];

let locationCounts = locationPeriodCounts.reduce(function(r, o) {
  let key = o.location;
  if (!helper[key]) {
    helper[key] = Object.assign({}, o); // create a copy of o
    helper[key].totalCountIn = 0;
    helper[key].totalCountOut = 0;
    r.push(helper[key]);
  } else {
    helper[key].totalCountIn += o.countIn;
    helper[key].totalCountOut += o.countOut;
  }
  return r;
}, []);

console.log(locationCounts);

This is what I'm looking for as output...

[
  {"location": "228", "totalCountIn": 18, "totalCountOut": 10},
  {"location": "229", "totalCountIn": 31, "totalCountOut": 18},
  {"location": "230", "totalCountIn": 7, "totalCountOut": 12}
];

Solution

  • I added an Object.entries to convert to object array

    We now need to spread the nested object to have both counters on the same level as the location

    let locationCounts = Object.entries(
      locationPeriodCounts
      .reduce(function (acc, { location, countIn, countOut }) {
        acc[location] ??= { totalCountIn: 0, totalCountOut: 0 }; // create if it does not exist
        acc[location].totalCountIn += countIn; // always add to the total
        acc[location].totalCountOut += countOut; // always add to the total
        return acc;
      }, {})
    )
    .map(([location, { totalCountIn, totalCountOut }]) => ({ location, totalCountIn, totalCountOut }));
    
    console.log(locationCounts);
    <script>
    let locationPeriodCounts = [
      {"period": "2023-10-21", "location": "228", "countIn": 6, "countOut": 1},
      {"period": "2023-10-22", "location": "228", "countIn": 8, "countOut": 2},
      {"period": "2023-10-23", "location": "228", "countIn": 3, "countOut": 3},
      {"period": "2023-10-24", "location": "228", "countIn": 1, "countOut": 4},
      {"period": "2023-10-21", "location": "229", "countIn": 5, "countOut": 6},
      {"period": "2023-10-22", "location": "229", "countIn": 18, "countOut": 6},
      {"period": "2023-10-23", "location": "229", "countIn": 8, "countOut": 6},
      {"period": "2023-10-24", "location": "230", "countIn": 3, "countOut": 6},
      {"period": "2023-10-25", "location": "230", "countIn": 4, "countOut": 6}
    ];
    </script>