Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create nested object based on dynamic keys

I need to transform some data into a special format. The tricky part is, that it needs to be grouped and nested by dynamic keys. As the raw data is flat.

Here is some simplified example data:

const data = [
  {
    category: 'Classic',
    price: 1,
    order: '1233-ABC'
    currency: 'EUR',
  },
  {
    category: 'Classic',
    price: 2,
    order: '1234-ABC'
    currency: 'EUR',
  },
  {
    category: 'Modern',
    price: 3,
    order: '1235-ABC'
    currency: 'USD',
  },
  {
    category: 'Classic',
    price: 4,
    order: '1236-ABC'
    currency: 'EUR',
  },
  {
    category: 'Modern',
    price: 5,
    order: '1237-ABC'
    currency: 'EUR',
  },
  {
    category: 'Classic',
    price: 6,
    order: '1238-ABC'
    currency: 'USD',
  },
  {
    category: 'Modern',
    price: 7,
    order: '1239-ABC'
    currency: 'USD',
  }
];

Now I want to define the groupings and the aggregate.

For example:

grouping = ['category'];
sum = ['price'];

So it should groupBy the category key and sum up all price key entries.

// Expected Output

[{
    category: 'Classic',
    price: 13
}, {
    category: 'Modern',
    price: 15
}]

I got this working with reduce.

Here is the code:

let groupingsField = ['category'];
let aggregateField = ['price']

let [group, ...rest] = groupingsField
let [sum, ...noone] = aggregateField

const groupedData = data.reduce((acc, current) => {
  acc.push({
    [group]: current[group],
    [sum]: data.filter(item => item[group] === current[group])
    .map(el => el[sum])
    .reduce((total, current) => total + current)
  })

  return acc
}, [])
.reduce((acc, current) => {
  const x = acc.find(item => item[group] === current[group])

  return !x ? acc.concat([current]) : acc
}, [])

However, I need the groupings to be dynamic and if more then 1 value is present there, it should create nested sub data sets. It should be possible to add multiple groupings.

// Example 2

grouping = ['category', 'currency'];
sum = ['price'];

// Expected Output

[{
    category: 'Classic',
    price: 13,
    sub: [{
        currency: 'EUR',
        price: 7
    }, {
        currency: 'USD',
        price: 6
    }]
}, {
    category: 'Modern',
    price: 15,
    sub: [{
        currency: 'EUR',
        price: 9
    }, {
        currency: 'USD',
        price: 10
    }]
}]

Thus, if I would add another key to the grouping

grouping = ['category', 'currency', 'something'];

It should be three levels deep nestes.

{
 category: 'Modern',
 price: 15,
 sub: [{
   currency: 'EUR',
   price: 9,
   sub: [{
     someting: ...
     price: ...
   }]
 }]
}

However, I couldn't get my head around this, how to add the nesting dynamically based on the groupings. I guess I need some recursion here.

Would love some help on this!

like image 241
Jakub Juszczak Avatar asked Nov 23 '25 07:11

Jakub Juszczak


1 Answers

Thanks for the help! However, after some sleep I came up with my own solution:


groups = ['category', 'currency']; // this can be dynamic 
sum = ['price'];

function createGroup (groups, data, sum, childNode = 'sub') {
    let [primaryGroup, ...rest] = groups;

    let groupedData = data.reduce((acc, current) => {
    let chunk = {
        [primaryGroup]: current[primaryGroup],
        [sum]: data.filter(item => item[primaryGroup] === current[primaryGroup])
        .map(el => el[sum])
        .reduce((total, current) => total + current),
       ...(rest.length > 0 ? {[childNode]: createGroup(rest, data)} : {})
    }

    acc.push(chunk)
    return acc
  }, [])
    .reduce((acc, current) => {
        const x = acc.find(item => item[primaryGroup] === current[primaryGroup])
        return !x ? acc.concat([current]) : acc
    }, [])

  return groupedData;
}

like image 192
Jakub Juszczak Avatar answered Nov 25 '25 23:11

Jakub Juszczak



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!