Given an array with objects of this form:
[{data: {set: 'set1', option: 'option1'}},
{data: {set: 'set1', option: 'option2'}},
{data: {set: 'set1', option: 'option3'}},
{data: {set: 'set2', option: 'optionA'}},
{data: {set: 'set2', option: 'optionB'}}]
How can I get an array that looks like this:
[{set: 'set1', options: ['option1', 'option2', 'option3']},
{set: 'set2', options: ['optionA', 'optionB']}]
I would like to use functional programming, Ramda or native JS methods. Thanks.
I like to think about questions like this in terms of a few steps that keep moving me toward my desired output. Sometimes this means that I miss a more elegant solution, but it usually makes it easier to come up with something that works.
So let's look at your problem this way using Ramda.
You don't want that outer data
property. Since your data is a list of objects, and each one has a single data
property holding the data we want, we can simply call prop('data')
on each one.
To call it on each one, we can use map
, giving us map(prop('data'))
.
Because this is such a common use, Ramda also supplies a function which combines map
and prop
this way: pluck('data')
. Applying that to your input we get:
[
{option: "option1", set: "set1"},
{option: "option2", set: "set1"},
{option: "option3", set: "set1"},
{option: "optionA", set: "set2"}
{option: "optionB", set: "set2"}
]
This is a good start. Now we need to think about combining them into like groups.
We want all the elements that share their set
property to be grouped together. Ramda offers groupBy
, which accepts a function that turns an item into a grouping key. We want to group by that set
property, so we can use prop
again, and call groupBy(prop('set'))
against the previous results.
This yields:
{
set1: [
{option: "option1", set: "set1"},
{option: "option2", set: "set1"},
{option: "option3", set: "set1"},
],
set2: [
{option: "optionA", set: "set2"}
{option: "optionB", set: "set2"}
]
}
There is redundant information in there. Somewhere we're going to need to figure that out. But I'll save that for a little bit while I try to pull together other parts.
We've already seen pluck
. It looks like we could use it on set1
and set2
. Well map
also works on objects, so if we simply call map(pluck('option'))
on that last we get this:
{
set1: ["option1", "option2", "option3"],
set2: ["optionA", "optionB"]
}
Oh look, that also got rid of the redundancy. This is looking pretty close to the desired output.
But now I don't see a built-in Ramda function that will get me all the way there. I could write a custom one. Or I could look to convert this in two steps. Knowing that I would like to use Ramda's zipObj
function, I can first convert the above to arrays via toPairs
, generating this:
[
["set1", ["option1", "option2", "option3"]],
["set2", ["optionA", "optionB"]]
]
and then I can map zipObj
over the results with the keys I want each property to have. That means I can call map(zipObj(['set', 'options']))
to get the final desired results:
[
{
set: "set1",
options: ["option1", "option2", "option3"]
},
{
set: "set2",
options: ["optionA", "optionB"]
}
]
All right, now we have to put these together. Ramda has pipe
and compose
. I usually choose compose
only when it fits on one line. So merging these with pipe
, we can write this:
const transform = pipe(
pluck('data'),
groupBy(prop('set')),
map(pluck('option')),
toPairs,
map(zipObj(['set', 'options']))
)
And then just call it as
transform(myObj)
You can see this in action on the Ramda REPL. On there you can comment out later lines inside the pipe
to see what the earlier ones do.
I built the code there, adding one line at a time to the pipe until I had transformed the data. I think this is a nice way to work.