Here's one approach:
const extractValues = months =>
Object .entries (months
.flatMap (({values}) => values)
.reduce ((a, {id, value}) => ({...a, [id]: [...(a [id] || []), value]}), {})
) .map (([id, values]) => ({id, values}))
const months = [{month: "April", values: [{id: "rice", value: 10}, {id: "wheat", value: 20}, {id: "corn", value: 30}]}, {month: "May", values: [{id: "rice", value: 40}, {id: "wheat", value: 50}, {id: "corn", value: 60}]}, {month: "June", values: [{id: "rice", value: 70}, {id: "wheat", value: 80}, {id: "corn", value: 90}]}]
console .log (extractValues (months))
.as-console-wrapper {max-height: 100% !important; top: 0}
We start by flat-mapping the original array to combine all the value nodes in a single array, so it will contain rice/10, wheat/20, corn/30, rice/40, ..., corn/90, in objects like your original: {id: 'rice', value: 10}.
Then with reduce, we combine those into
{
rice: [10, 40, 70],
wheat: [20, 50, 80],
corn: [30, 60, 90]
}
We use Object.entries to extract that into
[
['rice', [10, 40, 70]],
['wheat', [20, 50, 80]],
['corn', [30, 60, 90]]
]
and then we map over them, converting this into your expected output structure.
We retain the order of the inputs, and don't try to resort by your month. That would not be much more work, but it's a reasonable guess that the data will already be sorted that way.
Update
I was curious about the possibility that the data is less consistent, and thought I'd rewrite in a way that handled that. In this version, we also have one month that has soybean records, and June is in the array before May. So we need to sort on the months, and capture all the ids we might need in any of them. It looks like this:
const byMonth = ((months) => (a, b) =>
(months .indexOf (a .month) || 13) - (months .indexOf (b .month) || 13)
)(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'])
const extract = (
xs,
months = [... xs] .sort (byMonth),
ids = [... new Set (months .flatMap (({values}) => values .map (({id}) => id)))]
) => ids .map ((id) => ({
id,
values: months .map (m => (m .values .find (v => v.id == id) || {value: null}) .value)
}))
const months = [{month: "April", values: [{id: "rice", value: 10}, {id: "wheat", value: 20}, {id: "corn", value: 30}]}, {month: "June", values: [{id: "rice", value: 40}, {id: "wheat", value: 50}, {id: "corn", value: 60}]}, {month: "May", values: [{id: "rice", value: 70}, {id: "wheat", value: 80}, {id: "soybeans", value: 42}, {id: "corn", value: 90}]}]
console .log (extract (months))
.as-console-wrapper {max-height: 100% !important; top: 0}
Note that the output now has some nulls to note that there were no soybean records in April or June, only in May:
{
id: "soybeans",
values: [null, 42, null]
}
This sort of working around inconsistent data is a large part of my job, and of many developers I know.