I have JSON data:

var data = [ { dat: 'timestamp', val: 10 }, { dat: 'timestamp', val: 20}, (...)]

Data contains objects with timestamp and value, can be more than one in month.

I need to sum values from objects which have the same month and year.

First I use each function to add additional field cat which contains formatted date in format MMM'YY for example: Sep'15, after that I group the result by this cat field. This result I map to objects with properties name and value. Values are summed by using reduce function on plucked values - val from each grouped result. I'm using underscore.

var withCategories = _.each(data, function (elem) {
                            elem.cat = moment(elem.dat).format("MMM[']YY");

var groupedResult = _.groupBy(withCategories , 'cat');

var finalResult = _.map(groupedResult, function (elems) {

                            var valuesArray = _.pluck(elems, 'val'); // from group
                            return {
                                name: elems[0] ? elems[0].cat : '',
                                value: _.reduce(valuesArray, function (memo, num) {
                                    return memo + num;
                                }, 0)

1 Answer 1


First of all, your indentation is odd. Use a beautifier tool like JSBeautifier. If you use Sublime Text 3, there's an HTML-CSS-JS Prettifier plugin which uses the same tool internally.

Next, dat and val don't really tell anything about your data. Name it meaningfully. It's a timestamp, maybe date? And value for val? If you worry about size on transmission, just use gzip. It's better than losing hair on misnamed data.

Next, you're using moment.js in one of the loops. Moment.js is very slow based on personal experience. It tries to do as much as possible to get dates correctly, sacrificing execution speed. If you profile your code, you'll see a lot of internal extend calls in moment to generate your date. Since you're just getting year and month, why not use native getFullYear and getMonth?

Now you look like you're just operating on arrays. You can use the native array methods instead of underscore (unless you're still trying to support IE8???)

So if I understand your code correctly, this should do the same thing with lesser loops.

// Because JS doesn't have a nice way to name months because they may differ per locale
var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// Use reduce to aggregate your data. Pass around a hash so that we have
// direct access to groups as well as ensure groups appear just once.
var dataByMonth= data.reduce(function(dataByMonth, datum){
  var date  = new Date(datum.dat);
  var value = datum.val;
  var month = monthNames[date.getMonth()];
  var year  = ('' + date.getFullYear()).slice(-2);
  var group = month + '\'' + year;

  dataByMonth[group] = (dataByMonth[group] || 0) + value;

  return dataByMonth;
}, {});

// Now just turn the hash into an array.
var finalResult = Object.keys(dataObjectByMonth).map(function(group){
  return { name: group, value: dataByMonth[group] };

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.