Search code examples
javascriptarrayssortingdatetimeobject

Group Array of Objects to Year and Month by Date


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?


Solution

  • 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);