Sujeet
Sujeet

Reputation: 3810

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.

Upvotes: 8

Views: 3330

Answers (3)

Som Shekhar Mukherjee
Som Shekhar Mukherjee

Reputation: 8168

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

Upvotes: 6

Ravikumar
Ravikumar

Reputation: 2205

We can achieve this with simple reduce like below.

const data =[{  a: 1,  created_on: '2021-04-23 04:00:00',}, {  b: 1,  created_on: '2021-04-24 09:03:01',}, {  b: 1,  created_on: '2021-04-24 13:03:01',}];

const allSlots = ['00:00-04:00', '04:00-08:00', '08:00-12:00', '12:00-16:00','16:00-20:00','20:00-24:00'].map(ele => ele.split('-'));




const result = data.reduce((acc, ele) => {
  // use can use format() to custom formats
  const [date, time] =ele.created_on.split(' ');
  const slot = allSlots.find(slot => slot[0] <= time && slot[1] >= time );
  if (!acc[date]) {
    acc[date] = {};
  }
  const key = `${slot[0]}-${slot[1]}`;
  if (!acc[date][key]) {
    acc[date][key] = [];
  }
  acc[date][key].push(ele);
  return acc;
}, {});

console.log(JSON.stringify(result))

Upvotes: 0

Sujeet
Sujeet

Reputation: 3810

This is what I tried using momentjs and lodash.

const bookings = [{
  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',
}];


const groupedByDate = bookings.reduce((acc, e) => {
  const eDate = moment(e.created_on).format('YYYY-MM-DD');
  if (acc[eDate]) {
    acc[eDate].push(e)
  } else {
    acc[eDate] = [e];
  }
  return acc;
}, { })
const allSlots = ['00:00-04:00', '04:00-08:00', '08:00-12:00', '12:00-16:00','16:00-20:00','20:00-24:00'];
const grupedByDateAndSlots = _.mapValues(groupedByDate, (ele) => {
  return ele.reduce((acc, e) => {
    const createdOnDate = moment(e.created_on).format('YYYY-MM-DD');
    const foundSlot = allSlots.find((slot) => moment(e.created_on).isBetween(moment(`${createdOnDate} ${slot.split('-')[0]}`), moment(`${createdOnDate} ${slot.split('-')[1]}`)))
    if (acc[foundSlot]) {
      acc[foundSlot].push(e)
    } else {
      acc[foundSlot] = [e];
    }
    return acc;
  }, { })
})

console.log(grupedByDateAndSlots)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

Upvotes: 1

Related Questions