Jason Brill Jason Brill - 26 days ago 5
Javascript Question

Confused, Looping over nested Object and doing specific calculations

I am stuck! I have the following code:

const hours = [
{
"workHour" : "8",
"Item name 1" : 120,
"Item name 2" : 149,
"Item name 3" : 137
},
{
"workHour" : "9",
"Item name 1" : 134,
"Item name 2" : 119,
"Item name 3" : 144
},
{
"workHour" : "10",
"Item name 1" : 60,
"Item name 2" : 86,
"Item name 3" : 83
},
];

const records = [
{ date: new Date(), statistics: hours },
{ date: new Date(), statistics: hours },
{ date: new Date(), statistics: hours },
];

const result = {};
result.chart = [];

records.forEach((record) => {
const items = [];
record.statistics.forEach((hour) => {
for (let key in hour) {
if (key !== 'workHour') {
items.push({
key: hour[key]
})
}
}
});
result.chart.push({
date: record.date,
items
});
});

console.log(result);


What I'm trying to do is loop over each item in the records array, then loop over the hours of each record, sum together all values of every item, like calculating the total from all of the hours, the result I am tryin to get is:

array = [
{
date: //record date,
"Item name 1" : 314,
"Item name 2" : 354,
"Item name 3" : 364
},
{
date: //record date,
"Item name 1" : 314,
"Item name 2" : 354,
"Item name 3" : 364
},
{
date: //record date,
"Item name 1" : 314,
"Item name 2" : 354,
"Item name 3" : 364
}
];


Working example when the hours item keys are known:

const hours = [
{
"workHour" : "8",
"Item name 1" : 120,
"Item name 2" : 149,
"Item name 3" : 137
},
{
"workHour" : "9",
"Item name 1" : 134,
"Item name 2" : 119,
"Item name 3" : 144
},
{
"workHour" : "10",
"Item name 1" : 60,
"Item name 2" : 86,
"Item name 3" : 83
},


];

const records = [
{ date: new Date(), statistics: hours },
{ date: new Date(), statistics: hours },
{ date: new Date(), statistics: hours },
];

const result = {};
result.records = [];

const calculate = (profits, key) =>
profits
.filter(profit => profit !== null)
.reduce((prevVal, profitVal) => prevVal + profitVal[key], 0) || 0;

records.forEach((record) => {
const items = [];
const { statistics } = record;
result.records.push({
date: record.date,
'Item name 1': calculate(statistics, 'Item name 1'),
'Item name 2': calculate(statistics, 'Item name 2'),
'Item name 3': calculate(statistics, 'Item name 3'),
});
});

console.log(result);


Prints:

records = [


{
'Item name 1': 314,
'Item name 2': 354,
'Item name 3': 364,
},
{
'Item name 1': 314,
'Item name 2': 354,
'Item name 3': 364,
},
{
'Item name 1': 314,
'Item name 2': 354,
'Item name 3': 364,
},
]

Answer

I believe this solves your request, but I feel there is more to it. Are the records all intended to have the same hours?

I know it is a little dense, so I have add some comments that I hope shed some light on how this is working.

const hours = [
  { "workHour" : "8", "Item name 1" : 120, "Item name 2" : 149, "Item name 3" : 137 },
  { "workHour" : "9", "Item name 1" : 134, "Item name 2" : 119, "Item name 3" : 144 },
  { "workHour" : "10", "Item name 1" : 60, "Item name 2" : 86, "Item name 3" : 83 }
];

const records = [
   { date: new Date(), statistics: hours },
   { date: new Date(), statistics: hours },
   { date: new Date(), statistics: hours }
];

// ========================
// Using map()
// For each "record" return an object based on "record"
// ========================
var result = records.map(function(record){

  // ========================
  // Create the initial result that does not depend
  // on summing up the "statistics".
  //
  // We will pass this into the reduce() and it will be the
  // initial value of the accumulator
  // ========================
  var initialResult = {date: record.date};
  // ========================
  
  // ========================
  // Using reduce()
  // itterate over the interesting work items
  // summing up the hours
  // ========================
  var finalResult = record.statistics.reduce(function(acc, item){

    Object.keys(item)  // an array of keys in our item object
          .filter(function(key){ return key !== "workHour"; }) // remove this key
          .forEach(function(key) { acc[key] = (acc[key] || 0) + item[key] }); // for each remaining key, add its value to running total

    // ========================
    // return the running total accumulator for use against the next item
    // ========================
    return acc;
    // ========================

  }, initialResult);
  // ========================

  // ========================
  // return an object based on record but with accumulated data from the reduce()
  // this object becomes an item in the array ultimately returned by map()
  // ========================
  return finalResult;
  // ========================

});
// ========================

console.log(result)

Here is a version without reduce:

const hours = [
  { "workHour" : "8", "Item name 1" : 120, "Item name 2" : 149, "Item name 3" : 137 },
  { "workHour" : "9", "Item name 1" : 134, "Item name 2" : 119, "Item name 3" : 144 },
  { "workHour" : "10", "Item name 1" : 60, "Item name 2" : 86, "Item name 3" : 83 }
];

const records = [
   { date: new Date(), statistics: hours },
   { date: new Date(), statistics: hours },
   { date: new Date(), statistics: hours }
];

var result = records.map(function(record){
  var initialResult = {date: record.date};

  record.statistics.forEach(function(item){
    Object.keys(item)
          .filter(function(key){ return key !== "workHour"; })
          .forEach(function(key) { initialResult[key] = (initialResult[key] || 0) + item[key] });
  });

  return initialResult;
});

console.log(result)