Search code examples
javascriptjsonmomentjslodash

How can I use moment and lodash to group by objects fields and count by dates?


I have quite of a complex situation that I am not sure how to transform using lodash and moment. Say that I have a date range and this initial data.

var startDate = "2018-02-21"
var endDate = "2018-02-23"
var data = [
      {
       "date": "2018-02-21 21:21:17",
       "group": "A"
       },
      {
       "date": "2018-02-21 21:21:17",
       "group": "B"
       },
      {
       "date": "2018-02-23 21:21:17",
       "group": "A"
       },
      {
       "date": "2018-02-21 21:21:17",
       "group": "B"
       }
  ];

I would like to use lodash to group all the "group" fields and with a new field in the new object called "dates" which will be a key / value pair of dates. The keys will be the date range (from startDate to endDate) and the values will be the count of matching dates.

The new output would look like this:

 var output = [
    {
        "group": "A",
        "dates": [
            "2018-02-21": 1,
            "2018-02-22": 0
            "2018-02-23": 1
        ]
    },
    {
        "group": "B",
        "dates": [
            "2018-02-21": 2,
            "2018-02-22": 0,
            "2018-02-23": 0
        ]
    }
  ];

I created a jsfiddle and imported moment and lodash of this situation.

http://jsfiddle.net/dp7rzmw5/6467/

Thank you so much if you can help!


Solution

  • You can solve this problem with pure javascript.

    First you create an array of dates between your start and end dates. We will use this array later if there are missing days in data.

    var dates = [];
    var startDate = new Date("2018-02-21");
    var endDate = new Date("2018-02-23");
    for (var s = startDate; s <= endDate; s.setDate(s.getDate() + 1)) {
        dates.push(s.toISOString().split('T')[0]);
    }
    

    Now dates is an array: [ "2018-02-21", "2018-02-22", "2018-02-23" ].

    Second you can use .reduce() on your data to aggregate the desired values combining with .forEach() on the dates array in order to fill the day gap in case you don't have data for a specific day.

    var groups = [];
    var output = data.reduce((obj, item) => {
        var d = new Date(item.date).toISOString().split('T')[0];
        var x = groups.indexOf(item.group);
        if (x === -1) {
            groups.push(item.group);
            x = groups.indexOf(item.group);
        }
        obj[x] = obj[x] || {};
        obj[x]["date"] = obj[x]["date"] || {};
        dates.forEach(date => {
            if (!obj[x]["date"][date]) {
                obj[x]["date"][date] = 0;
            }
        });
        obj[x]["date"][d]++;
        obj[x]["group"] = item.group;
        return obj;
    }, {});
    

    The output is:

    {
      "0": {
        "date": {
          "2018-02-21": 1,
          "2018-02-22": 0,
          "2018-02-23": 1
        },
        "group": "A"
      },
      "1": {
        "date": {
          "2018-02-21": 2,
          "2018-02-22": 0,
          "2018-02-23": 0
        },
        "group": "B"
      }
    }
    

    Copy the above code in your web console and see the result of console.table(output).

    Also I modified your jsfiddle demo.