3

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!

4
  • If you're open to using libs.. Take a look at Lodash Commented May 9, 2019 at 17:32
  • Possible duplicate of Group objects by multiple properties in array then sum up their values Commented May 9, 2019 at 17:39
  • @PsyGik Did not found anything usefull in loadash to perform the dynamic n-deep nesting based on a key. Commented May 11, 2019 at 8:45
  • @KunalMukherjee Not exactly. Like I wrote I covered the use-case for only 1 aggregator and one grouping, myself. However I want this to work with multiple groupings that are nested. Commented May 11, 2019 at 8:47

2 Answers 2

1

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;
}

Sign up to request clarification or add additional context in comments.

Comments

0

Well... It was indeed a challenge but I think it's working... Maybe not on the most fancy way but I guess it will do.

var newArr = [];
var MyObj = {};
var MyObj2 = {};


    for( i = 0; i < data.length; i++){
        if (data[i].category + '|' + data[i].currency in MyObj) MyObj[data[i].category + '|' + data[i].currency] += data[i].price;
        else MyObj[data[i].category + '|' + data[i].currency] = data[i].price;
    }

    for (var key in MyObj) {
        let keyArr=-1;
        let split_key = key.split("|");


        for( i = 0; i < newArr.length; i++){
            console.log(newArr[i].category + '/' +split_key[0] + i);
            if (newArr[i].category == split_key[0]) {

                keyArr=i;
                break;
            }
        }

    if (keyArr<0){
        MyObj2['category'] = split_key[0];
        MyObj2['price'] = MyObj[key];
        MyObj2['sub'] = [{currency: split_key[1], price: MyObj[key]}];
        newArr.push(MyObj2);
    }else{
        newArr[keyArr]['price']+=MyObj[key];
        newArr[keyArr]['sub'].push({currency: split_key[1], price: MyObj[key]});
    }

        MyObj2={};

    }
    console.log(newArr);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.