Here's a variation without any reduce:
group_by(.item) |
map({
item: first.item,
attributes: (
group_by(.attributes[].type) |
map({
type: first.attributes[].type,
colour: ( map(.attributes[].colour[]) )
})
)
})
I start by grouping the original elements by their item key. This gives us an array that is grouped by item. Each element in that array consists of a sub-array containing all the original objects with the same item.
The first map() brings these groups together by creating one object for each group. The object has item and attributes keys, and the value for the item key is taken arbitrarily from the first element of the group (they are all the same).
A further group_by() and map() creates the value for the attributes key. This time grouping over the type key down in the attributes array of the original objects and, for each created group, collecting the type and colour values from the original objects.