1

I am trying to transform a JSON file that comes from an API with a structure similar to this:

{
"fruitType": {"name": "bananas"},
"plantedIn": {"country": "USA", "state": "Minnesota"},
"harvestMethod": {"name": "manual"},
"product": {"id": "841023781723"},
},
... (other fruits, or the same fruit planted/harvested differently)

Into something like this:

"bananas": {
    "USA": {
        "manual": {
            "841023781723": {
                "fruitType": {"name": "bananas"},
                "plantedIn": {"country": "USA", "state": "Minnesota"},
                "harvestMethod": {"name": "manual"},
                "product": {"id": "841023781723"},
            }
        }
    }
},
...

So, essentially I want to group first by fruitType name, then by plantedIn country, then by harvestMethod name, and finally product id.

After some research I found that lodash is popular for this kind of grouping. I developed a very naive solution with their chain, groupBy and mapValues methods like so:

const _ = require('lodash');
const groupedData = _.chain(data)
                    .groupBy('fruitType.name')
                    .mapValues(values => _.chain(values)
                        .groupBy('plantedIn.country')
                        .mapValues(values => _.chain(values)
                            .groupBy('harvestMethod.name')
                            .mapValues(values => _.chain(values)
                                .groupBy('product.id')
                                .value()
                            ).value()
                        ).value()
                    ).value()

This solution, however functional, feels very verbose and is likely inefficient. Therefore I would like to ask if there is any better alternative, either with loadash or any other way.

2 Answers 2

1

You could take an array of function to get the keys and build the structure.

const
    data = [{ fruitType: { name: "bananas" }, plantedIn: { country: "USA", state: "Minnesota" }, harvestMethod: { name: "manual" }, product: { id: "841023781723" } }],
    keys = [o => o.fruitType.name, o => o.plantedIn.country, o => o.harvestMethod.name, o => o.product.id],
    result = data.reduce((r, o) => {
        keys.reduce(
            (q, fn, i, { length }) => q[fn(o)] ??= i + 1 === length ? o : {},
            r
        );
        return r;
    }, {});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

4
  • Btw how different would it be if I wanted to wrap each group key in a array? For instance "bananas": ["USA": [...]] Commented Jul 22, 2022 at 18:50
  • at least you need an object with the keys. do you want to group with arrays? Commented Jul 22, 2022 at 19:28
  • I was just wondering if it was possible because it would simplify consuming the data in the frontend Commented Jul 22, 2022 at 21:51
  • what result do you rxpect? maybe ask a new qustion? Commented Jul 23, 2022 at 6:07
1

There is already an answer but here is maybe a more readable solution

const grouped = items.reduce((acumm, current, index) => {
    acumm[current.fruitType.name] = {
      [current.plantedIn.country]: {
        [current.harvestMethod.name]: {
          [current.product.id]: current,
          ...(acumm[current.fruitType.name]?. 
                [current.plantedIn.country]?. 
                [current.harvestMethod.name] ?? {}
          ),
        },
      },
    };
    return acumm;
}, {});

Stackblitz Example

1
  • Thank you for your solution. I will be accepting Nina's because it is more modular. Commented Jul 22, 2022 at 17:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.