Home > Blockchain >  Group Array of Objects to Year and Month by Date
Group Array of Objects to Year and Month by Date

Time:04-05

Given the following Array of Objects:

[{
    "id": 1,
    "name": "random_name1",
    "published_at": "2021-01-16T08:52:24.408Z",
},
{
    "id": 2,
    "name": "random_name2",
    "published_at": "2022-02-16T08:52:24.408Z",
},
{
    "id": 3,
    "name": "random_name3",
    "published_at": "2020-04-16T08:52:24.408Z",
},
{
    "id": 4,
    "name": "random_name4",
    "published_at": "2020-04-16T08:52:24.408Z",
},
{
    "id": 5,
    "name": "random_name5",
    "published_at": "2022-05-16T08:52:24.408Z",
}
]

I need to group the items in one array of nested objects (descending) by Year and Month, result should be:

 [
  {
    year: '2022',
    months: [
      {
        month: '5',
        items: [
          {
            id: '5',
            name: 'random_name5'
          }
        ]
      },
      {
        month: '2',
        items: [
          {
            id: '2',
            name: 'random_name2'
          }
        ]
      }
    ]
  },
  {
    year: '2021',
    months: [
      {
        month: '1',
        items: [
          {
            id: '1',
            name: 'random_name1'
          }
        ]
      },
      {
        month: '2',
        items: [
          {
            id: '2',
            name: 'random_name2'
          }
        ]
      }
    ]
  },
  {
    year: '2020',
    months: [
      {
        month: '4',
        items: [
          {
            id: '3',
            name: 'random_name3'
          },
          {
            id: '4',
            name: 'random_name4'
          }
        ]
      }
    ]
  }
];

I have tried the following:

items = [...new Set(items.map((item) => parseInt(item.published_at.split('-')[0])))].map((year) => [
  {
    year: year,
    months: [
      ...new Set(
        items
          .filter((item) => parseInt(item.published_at.split('-')[0]) === year)
          .map((item) => parseInt(item.published_at.split('-')[1]))
      )
    ].map((month) => [
      {
        month: month,
        items: items.filter(
          (item) => parseInt(item.published_at.split('-')[0]) === year && parseInt(item.published_at.split('-')[1]) === month
        )
      }
    ])
  }
]);

return items

The problem with the above solution, is that it will create a two dimensional array like so (months being two dimensional too):

[
  [ { year: 2022, months: [Array] } ],
  [ { year: 2021, months: [Array] } ],
  [ { year: 2020, months: [Array] } ],
  [ { year: 2019, months: [Array] } ],
  [ { year: 2018, months: [Array] } ]
]

How to fix this?

CodePudding user response:

If you get a unique list of year-months you can use this to map your object

const items = [{ "id": 1,"name": "random_name1","published_at": "2021-01-16T08:52:24.408Z", },
{ "id": 2, "name": "random_name2", "published_at": "2022-02-16T08:52:24.408Z",},
{ "id": 3, "name": "random_name3","published_at": "2020-04-16T08:52:24.408Z",},
{"id": 4, "name": "random_name4", "published_at": "2020-04-16T08:52:24.408Z",},
{ "id": 5, "name": "random_name5", "published_at": "2022-05-16T08:52:24.408Z",}]
let uniqueYearMonths = [... new Set(items.map(x => x.published_at.substring(0,7)))];
let results = [... new Set(items.map(x => x.published_at.substring(0,4)))]
  .map(year => ({
    year: year,
    months: uniqueYearMonths
      .filter(ym => ym.startsWith(year))
      .map(ym => ({ 
        month: ym.substring(5,7),
        items: items
          .filter(item => item.published_at.startsWith(ym))
          .map(item => ({
            id: item.id,
            name: item.name
          }))
      }))
    }));

console.log(results);

CodePudding user response:

Given you array as data, you could do something with array methods like map and reduce.

Like this:

const groupedByYear = data.map((e) => ({ ...e, published_at: new Date(e.published_at) }))
.reduce((acc, e) => {
  const year = e.published_at.getFullYear();
  const month = e.published_at.getMonth()   1;
  if (!acc[year]) acc[year] = { year };
  if (!acc[year][month]) acc[year][month] = [];
  acc[year][month] = e;
  return acc;
}, {})

const result = Object.values(groupedByYear).reduce((acc, e) => {
  const { year, ...months } = e;
  acc.push({ year: year, months: months });
  return acc;
}, [])

This is an example and is probably not the best way to do this. It is only intended to show you a path of data transformations.

First data.map to be able to do operations on dates. Then a reduce to group data (here using an object). Then creating an array from the object values to match the output you want.

Compared to a solution like you showed, there is the advantage that you limit the number of times that you iterate over the array. It is always a good idea to avoid iterating to much time on an array for better performance.

  • Related