I would start by breaking this down into smaller functions. I might end up with a solution like this
const makeObject = kvs =>
kvs .reduce ( (a, {text, value} ) => ( {...a, [text]: value} ), {})
const makeFeatures = ( {available, userOptions: uo, ...rest} ) =>
( {...rest, features: {
...( available ? {available} : {}),
...( uo ? {userOptions: makeObject (uo) } : {} )
} } )
const transform = (people) =>
people.reduce( (a, {name, ...rest} ) => ( {...a, [name]: makeFeatures ({...rest}) }), {})
const people = [{"available": "true", "comment": "n1", "control": "1", "name": "John", "value": "v1"}, {"available": "true", "comment": "n2", "control": "2", "name": "Peter", "type": "integer", "userOptions": [{"text": "Utah", "value": "UT"}, {"text": "New York", "value": "NY"}], "value": "v2"}]
console .log (
transform (people)
)
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Thought Process
I would start this process from the outside in, first writing a version of transform like this:
const transform = (people) =>
people.reduce( (a, {name, ...rest} ) => ( {...a, [name]: {...rest} }), {})
which simply converts people to
{
John: {...}
Peter: {...}
}
and those internal object have all the original fields except for name.
Then I would write a version of makeFeatures that looks something like:
const makeFeatures = ( {available, ...rest} ) =>
( {...rest, features: {available} } )
so that makeFeatures(people[1]) would result in something like
{
comment: "n1",
control: "1",
features: {
available: "true"
},
value: "v1"
}
and for people[2] it would also include userOptions and type.
Then I can adjust transform like this:
const transform = (people) =>
people.reduce( (a, {name, ...rest} ) => ( {...a, [name]: makeFeatures({...rest}) }), {})
so that the main function will create our name-based object with available extracted into features for each person.
Now that available is there, we need to handle userOptions as well. This one is slightly more complicated, as it may or may not be included. So I change makeFeatures like this:
const makeFeatures = ( {available, userOptions, ...rest} ) =>
( {...rest, features: {available, ...( userOptions ? {userOptions} : {} )} } )
This includes userOptions insidefeaturesonly if it's in the person object. Seeing this makes me wonder ifavailable` might also only sometimes be included, and to be safe, I will rewrite like this:
const makeFeatures = ( {available, userOptions, ...rest} ) =>
( {...rest, features: {
...( available ? {available} : {}),
...( userOptions ? {userOptions} : {} )
} } )
So now the only thing left to do is convert the userOptions object from [{text, value}, {text2, value2}, ...] into {text: value, text2: value2, ...}. We write a new function for that:
const convertOptions = userOptions =>
userOptions .reduce ( (a, {text, value} ) => ( {...a, [text]: value} ), {})
and we alter makeFeatures to use it:
const makeFeatures = ( {available, userOptions, ...rest} ) =>
( {...rest, features: {
...( available ? {available} : {}),
...( userOptions ? {userOptions: convertOptions (userOptions) } : {} )
} } )
We now have working code. It meets the specs as we have them so far, and it's fairly easy to see how we could adjust to new requirements.
But there are two bits of cleanup I would still like to do. First, all the uses of "userOptions" inside makeFeatures seems untidy. I would add an abbreviation like this:
const makeFeatures = ( {available, userOptions: uo, ...rest} ) =>
( {...rest, features: {
...( available ? {available} : {}),
...( uo ? {userOptions: convertOptions (uo) } : {} )
} } )
This is a minor consideration, and some people would not approve, but I find it easier on the eyes.
Second, we can note that convertOptions has nothing to do with options. It's a generic function that takes an array of text/value pairs and converts them into an object. So I would rename that to makeObject, and possibly move it from this block to some utility code.
While this all sounds long and involved, all these steps together took under ten minutes. I was running it in a REPL with instant feedback of my changes, and it really wasn't complicated. Writing it up here took much more time.
Array.reduce()is typically used here. Also as you are moving tounique namesyou might want to start withSet()first to get all unique names, that would make things a little easier.Array.reduce. Lodash has a method for this called groupBy_.groupBy(peopleArray, "name"). You could also refer to their implementation. lodash.com/docs/#groupBygroupBywill create arrays of elements grouped under each key.reduceis easy enough here.