JavaScript transform flat array to nested / grouped and sorted array

Viewed 90

I am trying to transform an array of objects to a 'grouped' and sorted output array in JavaScript, preferably the ES6-way (e.g. using .map, .reduce, etc.).

Summary:

  • array of objects
  • each object is an 'item': a1,a2,b1,b2,...
  • each object has a 'section' property which is like a group/category
  • the desired output array should return the item objects grouped by 'section'
  • the sorting order of the output arrays of sections and items is defined in a property as well
const data = [
    { section: 'A', item: 'a1', section_order: 2, item_order: 2 },
    { section: 'B', item: 'b1', section_order: 1, item_order: 2 },
    { section: 'A', item: 'a2', section_order: 2, item_order: 1 },
    { section: 'B', item: 'b2', section_order: 1, item_order: 1 }
];


const desiredOutput = [
    {
        section: 'B',  // should come first as section_order = 1
        items: [
            { section: 'B', item: 'b2', section_order: 1, item_order: 1 },  // should come first as item_order = 1
            { section: 'B', item: 'b1', section_order: 1, item_order: 2 },   // should come second as item_order = 2
        ]
    },
    {
        section: 'A',   // should come second as section_order = 2
        items: [
            { section: 'A', item: 'a2', section_order: 2, item_order: 1 },
            { section: 'A', item: 'a1', section_order: 2, item_order: 2 },
        ]
    }
];

I saw an example that gets me somewhere in the right direction - but not exactly to the desired output and without catering for the sorting-order:

const result = data.reduce((accum, currElm) => {
    const currSection = currElm['section'];
    accum[currSection] = (accum[currSection] || []).concat(currElm);
    return accum;
}, {});
1 Answers

You should sort your data first. The sort function look like this

data.sort((a, b) => (a.section_order - b.section_order) || (a.item_order - b.item_order));

As you can see, we split the condition into 2 parts:

  1. (a.section_order - b.section_order)
  2. (a.item_order - b.item_order)

It means that you sort section_order then by item_order.

After that, you can loop just one time by using dictionary power like below

const data = [
    { section: 'A', item: 'a1', section_order: 2, item_order: 2 },
    { section: 'B', item: 'b1', section_order: 1, item_order: 2 },
    { section: 'A', item: 'a2', section_order: 2, item_order: 1 },
    { section: 'B', item: 'b2', section_order: 1, item_order: 1 }
];
data.sort((a, b) => (a.section_order - b.section_order) || (a.item_order - b.item_order));

var result = [];
for(var item of data){
  var section = item.section;
  if(!result[section]){ // if not exists => create new 
    result[section] = { section, items:[item] };
  }else{ // if exists => add one more item into items 
   result[section].items.push(item);
  }
}
console.log(Object.values(result));

Old version using reduce & map

const data = [
    { section: 'A', item: 'a1', section_order: 2, item_order: 2 },
    { section: 'B', item: 'b1', section_order: 1, item_order: 2 },
    { section: 'A', item: 'a2', section_order: 2, item_order: 1 },
    { section: 'B', item: 'b2', section_order: 1, item_order: 1 }
];
data.sort((a, b) => (a.section_order - b.section_order) || (a.item_order - b.item_order));

const accumData = data.reduce((accum, currElm) => {
    const currSection = currElm['section'];
    accum[currSection] = (accum[currSection] || []).concat(currElm);
    return accum;
}, {}); 


var result = Object.entries(accumData).map(([key, value]) => ({section: key, items: value}));
console.log(result);

Related