25

I have an array with objects, like the following.

b = {
  "issues": [{
    "fields": {
      "status": {
        "id": "200",
        "name": "Backlog"
      }
    }
  }, {
    "fields": {
      "status": {
        "id": "202",
        "name": "close"
      }
    }
  }, {
    "fields": {
      "status": {
        "id": "201",
        "name": "close"
      }
    }
  }]
};

I want to count how many issues have status close, and how many have backlog. I'd like to save the count in a new array as follows.

a = [
  {Name: 'Backlog', count: 1},
  {Name: 'close', count: 2}
];

I have tried the following.

b.issues.forEach(function(i) {
  var statusName = i.fields.status.name;

  if (statusName in a.Name) {
    a.count = +1;
  } else {
    a.push({
      Name: statusName,
      count: 1
    });
  }
});

That however doesn't seem to be working. How should I implement this?

Just a student
  • 10,560
  • 2
  • 41
  • 69
Shayuh
  • 361
  • 1
  • 3
  • 11

4 Answers4

57

This is a perfect opportunity to use Array#reduce. That function will take a function that is applied to all elements of the array in order and can be used to accumulate a value. We can use it to accumulate an object with the various counts in it.

To make things easy, we track the counts in an object as simply {name: count, otherName: otherCount}. For every element, we check if we already have an entry for name. If not, create one with count 0. Otherwise, increment the count. After the reduce, we can map the array of keys, stored as keys of the object, to be in the format described in the question. See below.

var b = {
  "issues": [{
    "fields": {
      "status": {
        "id": "200",
        "name": "Backlog"
      }
    }
  }, {
    "fields": {
      "status": {
        "id": "202",
        "name": "close"
      }
    }
  }, {
    "fields": {
      "status": {
        "id": "201",
        "name": "close"
      }
    }
  }]
};

var counts = b.issues.reduce((p, c) => {
  var name = c.fields.status.name;
  if (!p.hasOwnProperty(name)) {
    p[name] = 0;
  }
  p[name]++;
  return p;
}, {});

console.log(counts);

var countsExtended = Object.keys(counts).map(k => {
  return {name: k, count: counts[k]}; });

console.log(countsExtended);
.as-console-wrapper {
  max-height: 100% !important;
}

Notes.

  1. Array#reduce does not modify the original array.
  2. You can easily modify the function passed to reduce to for example not distinguish between Backlog and backlog by changing

    var name = c.fields.status.name;
    

    into

    var name = c.fields.status.name.toLowerCase();
    

    for example. More advanced functionality can also easily be implemented.

Just a student
  • 10,560
  • 2
  • 41
  • 69
  • does this change the original array? I want the result in a different array to the orginal.. – Shayuh Jun 06 '17 at 10:45
  • This is EXACTLY what reduce is supposed to be used for. No, it doesn't change the original array. – some Jun 06 '17 at 10:46
  • I tried your code .. (i have updated my code) but it says counts.map is not a function. Note: I did change a few things.. like remove the object keys function and changed the arrow function .. could u pls check it.. – Shayuh Jun 07 '17 at 04:59
  • Your problem is having removed `Object.keys`. The `counts` are stored in an object, while `map` is defined for arrays. We take the keys of the object (all names that were encountered) in an array and map that to the format you want. There is no such function directly for objects. – Just a student Jun 07 '17 at 05:46
  • @Y.Hewa I have rolled back your edit to keep the question clean, hope you don't mind. The issue should be resolved when you add `Object.keys` back. Just out of curiosity, why did you remove it? – Just a student Jun 07 '17 at 06:35
  • @Justastudent - If I want access one more field ID along with count and name, How can I do that, Could you please add Edit1 or something. Thank you – R15 Feb 09 '21 at 10:22
  • That depends on what you want the value of that field to be, @R15. Maybe it's better to ask a separate question. Feel free to link it from here. – Just a student Feb 09 '21 at 10:46
  • @Justastudent - Asked https://stackoverflow.com/questions/66117408/how-can-i-group-an-array-and-get-count-of-all-the-records-having-similar-type-or – R15 Feb 09 '21 at 10:47
3

Using ES6 Arrow functions you can do it with minimum syntax

var b = {
    "issues": [{
        "fields": {
            "status": {
                "id": "200",
                "name": "Backlog"
            }
        }
    }, {
        "fields": {
            "status": {
                "id": "202",
                "name": "close"
            }
        }
    }, {
        "fields": {
            "status": {
                "id": "201",
                "name": "close"
            }
        }
    }]
};

var countOfBackLog = b.issues.filter(x => {
return x.fields.status.name === "Backlog"
}).length

var countOfClose = b.issues.filter(x => {
return x.fields.status.name === "close"
}).length

a =[{Name: 'Backlog', count : countOfBackLog}, {Name: 'close', count : countOfClose}]

More about arrow functions here

Shivam Tiwari
  • 345
  • 3
  • 11
1

You can write like this. It is dynamic.

var a = {}; 
for(var key in b["issues"]){ 
    if(!a.hasOwnProperty(b["issues"][key].fields.status.name)){
     a[b["issues"][key].fields.status.name] = 1;
    }else{
     a[b["issues"][key].fields.status.name] = a[b["issues"][key].fields.status.name]+1;
    }
}
var c = [];
for(var key1 in a){
   c.push({
   name  : key1,
   count : a[key1]
   });
}
Ronak Patel
  • 651
  • 6
  • 19
0

Something like this should do the trick. Simply iterate over your data, keep 2 counters with the number of each type of issue, and create the data format you want in the end. Try it live on jsfiddle.

var b = {
    "issues": [{
        "fields": {
            "status": {
                "id": "200",
                "name": "Backlog"
            }
        }
    }, {
        "fields": {
            "status": {
                "id": "202",
                "name": "close"
            }
        }
    }, {
        "fields": {
            "status": {
                "id": "201",
                "name": "close"
            }
        }
    }]
};

var data = [];
for(var issue of b.issues){
    var entryFound = false;
    var tempObj = {
        name: issue.fields.status.name,
        count: 1
    };

    for(var item of data){
        if(item.name === tempObj.name){
        item.count++;
        entryFound = true;
        break;
      }
    }

    if(!entryFound){
        data.push(tempObj);
    }
}
console.log(data);
Sotiris Kiritsis
  • 3,178
  • 3
  • 23
  • 31
  • The thing is if there are around 10 statuses i would have to write if conditions for all of them.. is there a way to get the current status and check with the array like how ive tried to do .. ? – Shayuh Jun 06 '17 at 10:42
  • @Y.Hewa Check my updated answer. This should cover your case. You can iterate over your input and if you find it already in your list simply increase its number count by one, if not simply push it as a new item to your data array. – Sotiris Kiritsis Jun 06 '17 at 10:47