Jim Dover
Jim Dover

Reputation: 623

Using reduce to loop through JSON

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

Answers (2)

Shaun Luttin
Shaun Luttin

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

Abhinav Galodha
Abhinav Galodha

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

Related Questions