Summary
const transform = (array) => Object.values(array.reduce(
(all, curr) => {
const key = curr.item.id;
(all[key] || (all[key] = [])).push(curr);
return all;
}, {}
))
Using a Library
Using Ramda (disclaimer: I'm a Ramda author), I would do it like this, without thinking twice:
const transform = compose(values, groupBy(path(['item', 'id'])))
const results = transform(array)
Converting Library Code
Assuming we wouldn't want to include a library for this problem, I would work from that solution step-by-step. We first need to remove the compose, which is easy with just two functions. We first add an explicit parameter:
const transform = (array) => compose(values, groupBy(path(['item', 'id'])))(array),
and then wrap the two calls in the compose:
const transform = (array) => Object.values(groupBy(path(['item', 'id']), array))
Then we can easily replace path(['item', 'id']) with obj => obj.item.id. Note that we lose a little safety here. We could add an exception if there is no item property. But doing it safely would add a lot more machinery.
const transform = (array) => Object.values(groupBy(obj => obj.item.id, array))
Then we could write a simple version of groupBy:
const groupBy = (fn) => (list) => list.reduce(
(all, curr) => {
const key = fn(curr);
(all[key] || (all[key] = [])).push(curr);
return all;
},
{}
)
and replace the Ramda groupBy with that, folding in our simple path replacement with the values we prefer:
const transform = (array) => Object.values(array.reduce(
(all, curr) => {
const key = (obj => obj.item.id)(curr);
(all[key] || (all[key] = [])).push(curr);
return all;
}, {}
))
And with a little simplification, we get
const transform = (array) => Object.values(array.reduce(
(all, curr) => {
const key = curr.item.id;
(all[key] || (all[key] = [])).push(curr);
return all;
}, {}
))
const array = [{"item": {"id": 111, "name": "item1"}, "qty": 1}, {"item": {"id": 222, "name": "item2"}, "qty": 2}, {"item": {"id": 222, "name": "item3"}, "qty": 3}];
console.log(transform(array));
Building our own Mini-Library
Alternatively, we could actually create equivalents of those Ramda function, as they could be useful in all sorts of ways:
const groupBy = (fn) => (list) => list.reduce(
(all, curr) => {
const key = fn(curr);
(all[key] || (all[key] = [])).push(curr);
return all;
},
{}
)
const values = obj => Object.values(obj);
const pipe = (f1, ...fns) => (...args) => {
return fns.reduce((res, fn) => fn(res), f1.apply(null, args));
};
const path = (nodes) => (obj) => nodes.reduce((o, node) => o[node], obj)
const transform = pipe(groupBy(path(['item', 'id'])), values)
const array = [{"item": {"id": 111, "name": "item1"}, "qty": 1}, {"item": {"id": 222, "name": "item2"}, "qty": 2}, {"item": {"id": 222, "name": "item3"}, "qty": 3}]
console.log(transform(array))
(Note that I switched from compose to pipe, simply because it is easier to implement quickly. They have the same behavior but take their lists of functions in opposite order.)
_.values(_.groupBy(array, 'item.id'))