Reputation: 623
I'm using a reduce function which loops through my JSON file and sums together key values that I specify.
This works fine, until I remove one of the values.
My JSON is:
[{
"id": 100,
"jobNumber": 1,
"jobTasks": [{
"id": 12,
"cost": {
"amountString": 100
}
},
{
"id": 13,
"cost": {
"amountString": 500
}
}
]
},
{
"id": 101,
"jobNumber": 2,
"jobTasks": [{
"id": 14,
"cost": {
"amountString": 100
}
},
{
"id": 15
}
]
}
]
The reduce function I'm using is:
json.data.forEach(function(item) {
var sum = item.jobTasks.reduce(function(sum, elem) {
return sum + elem.cost.amountString;
}, 0);
console.log('jobNumber' + item.jobNumber + ' ' + sum);
});
I'm trying to achieve this:
jobNumber1 600
jobNumber2 100
This will work fine if I put another cost.amountString in the second job, however with two in the first and one in the second as shown in my JSON above, I get the following error:
TypeError: Cannot read property 'amountString' of undefined
Does reduce need more than one or the same amount of value pairs to work? I have tried various for loops without success.
Upvotes: 1
Views: 4037
Reputation: 141542
TypeError: Cannot read property 'amountString' of undefined
That's happening because your are trying to dereference an undefined value. You are essentially writing return sum + undefined.amountString
, which is similar to writing return sum + null.amountString
. How do you get around this?
In addition to what Agalo suggested, we can filter on jobTask objects that do have a cost:
json.data.forEach(function(item) {
var sum = item.jobTasks
.filter(function (j) { return j.cost; })
.reduce(function(sum, elem) {
return sum + elem.cost.amountString;
}, 0);
console.log('jobNumber' + item.jobNumber + ' ' + sum);
});
As an aside, consider using the arrow syntax, because it is arguably more readable.
json.data.forEach((item) => {
var sum = item.jobTasks
.filter((j) => j.cost)
.reduce((sum, elem) => sum + elem.cost.amountString, 0);
console.log('jobNumber' + item.jobNumber + ' ' + sum);
});
Complete code listing:
var json = {};
json.data = [{
"id": 100,
"jobNumber": 1,
"jobTasks": [{
"id": 12,
"cost": {
"amountString": 100
}
},
{
"id": 13,
"cost": {
"amountString": 500
}
}
]
},
{
"id": 101,
"jobNumber": 2,
"jobTasks": [{
"id": 14,
"cost": {
"amountString": 100
}
},
{
"id": 15
}
]
}
];
// with traditional functions
json.data.forEach(function(item) {
var sum = item.jobTasks
.filter(function(j) {
return j.cost;
})
.reduce(function(sum, elem) {
return sum + elem.cost.amountString;
}, 0);
console.log('jobNumber' + item.jobNumber + ' ' + sum);
});
// with arrow functions
json.data.forEach((item) => {
var sum = item.jobTasks
.filter((j) => j.cost)
.reduce((sum, elem) => sum + elem.cost.amountString, 0);
console.log('jobNumber' + item.jobNumber + ' ' + sum);
});
Upvotes: 1
Reputation: 9878
You need to check for undefined
values for the second object which doesn't have a property amountString
. In case, the property amountString
is not present in the object then return value 0
.
So change the return
statement from
return sum + elem.cost.amountString;
To
return sum + (elem.cost && elem.cost.amountString ? elem.cost.amountString : 0) ;
Complete Code:
var json = {};
json.data = [{
"id": 100,
"jobNumber": 1,
"jobTasks": [{
"id": 12,
"cost": {
"amountString": 100
}
},
{
"id": 13,
"cost": {
"amountString": 500
}
}
]
},
{
"id": 101,
"jobNumber": 2,
"jobTasks": [{
"id": 14,
"cost": {
"amountString": 100
}
},
{
"id": 15
}
]
}
]
json.data.forEach(function(item) {
var sum = item.jobTasks.reduce(function(sum, elem) {
return sum + (elem.cost && elem.cost.amountString ? elem.cost.amountString : 0) ;
}, 0);
console.log('jobNumber' + item.jobNumber + ' ' + sum);
});
Upvotes: 2