Search code examples
javascriptlodash

group by array of objects with condition and custom key


I have below array of objects,

[{

    a: 1,
    created_on: '2021-04-23 10:00:01',
}, {
    b: 1,
    created_on: '2021-04-24 09:03:01',
}, {
    b: 1,
    created_on: '2021-04-24 13:03:01',
}]

First, I need to group by with date ignoring times.

So,

{
 "2021-04-23": [{
    a: 1,
    created_on: '2021-04-23 10:00:01',
  }],
....
}

If it were a date like created_on: '2021-04-23', I could use lodash/groupBy.

I still could use it but now it involves some condition to be applied to convert those created_on into date format.

Further, I also need to group by those each grouped by date result as such that their created_on falls within a time interval.

"SALES_SLOTS": [
    {
      "slot": "00:00-04:00"
    },
    {
      "slot": "04:00-08:00"
    }, 
    {
      "slot":"08:00-12:00"
    }, 
    {
      "slot":"12:00-16:00"
    },
    {
      "slot":"16:00-20:00"
    },
    {
      "slot":"20:00-24:00"
    }
  ],

So, if created_on: '2021-04-23 10:00:01', it should come in the group,

{
  "08:00-12:00": [{
     a: 1,
     created_on: '2021-04-23 10:00:01',
  }]
}

So, first results should be grouped by date from created on and then elements in each groups should be further group by defined time slots.

I suppose, if I could use group by condition, I could achieve the desired result.


Solution

  • Vanilla JS


    Using Array#reduce with Map.

    1. Grouping by date is fairly simple, you can group the data by created_on.slice(0, 10) using a Map.

    2. Then I've created a helper function getSlot that returns a slot based on the time passed. Now, since your slots are fairly wide, testing just the hour part would be sufficient.

    3. Next, for every group of date, again group it by time using getSlot helper and Map and using Object.fromEntries you can get the desired object.

    NOTE: I've used expressions instead of statements inside my code (using commas, defaulted parameters) which is just a preferential choice.

    const 
      arr = [{ a: 1, created_on: "2021-04-23 10:00:01" }, { b: 1, created_on: "2021-04-24 09:03:01" }, { b: 1, created_on: "2021-04-24 13:03:01" }],
      
      slots = [{ slot: "00:00-04:00" }, { slot: "04:00-08:00" }, { slot: "08:00-12:00" }, { slot: "12:00-16:00" }, { slot: "16:00-20:00" }, { slot: "20:00-24:00" }],
          
      grpDate = Array.from(
        arr.reduce(
          (m, o, _i, _arr, d = o.created_on.slice(0, 10)) => (
            m.has(d) ? m.get(d).push(o) : m.set(d, [o]), m
          ),
          new Map()
        )
      ),
      
      getSlot = (time) =>
        slots
          .find(({ slot }) => time >= slot.slice(0, 2) && time < slot.slice(6, 8))
          .slot,
          
      grpSlot = grpDate.map(([k, v]) =>
        [k, Object.fromEntries(v.reduce(
          (m, o, _i, _arr, slot = getSlot(o.created_on.slice(11, 13))) => (
            m.has(slot) ? m.get(slot).push(o) : m.set(slot, [o]), m
          ),
          new Map()
        ))]
      );
    
    console.log(Object.fromEntries(grpSlot));

    Screenshot of the output


    enter image description here