Reputation: 3327
I have an array of object like below ,
[
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [],
"selectedValues": []
}
]
also if i iterate the object, the "values" key present in the object, it will convert it into like below,
[
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"selectedValues": ["x", "y"]
}
]
I tried with ramda functional programming but no result,
let findValues = x => {
if(R.has('values')(x)){
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
R.map(R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}))
}
}
R.map(findValues, arrObj);
Upvotes: 1
Views: 3970
Reputation: 50787
First of all, you need to be careful with your verbs. Ramda will not modify your data structure. Immutability is central to Ramda, and to functional programming in general. If you're actually looking to mutate your data, Ramda will not be your toolkit. However, Ramda will transform it, and it can do so in a relatively memory-friendly way, reusing those parts of your data structure not themselves transformed.
Let's first look at cleaning up several problems in your function:
let findValues = x => {
if (R.has('values')(x)) {
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
R.map(R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}))
}
}
The first, most obvious issue is that this function does not return anything. As mentioned above, Ramda does not mutate your data. So a function that does not return something is useless when supplied to Ramda's map
. We can fix this by returning the result of the map(evolve)
call and returning the original value when the if
condition fails:
let findValues = x => {
if (R.has('values')(x)) {
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
return R.map(R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}))
}
return x;
}
Next, the map
call makes no sense. evolve
already iterates the properties of the object. So we can remove that. But we also need to apply evolve
to your input value:
let findValues = x => {
if (R.has('values')(x)){
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
return R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}, x)
}
return x;
}
There's one more problem. Evolve
expects an object containing functions that transform values. For instance,
evolve({
a: n => n * 10,
b: n => n + 5
})({a: 1, b: 2, c: 3}) //=> {a: 10, b: 7, c: 3}
Note that the values of the properties in the object supplied to evolve
are functions. This code is supplying values. We can wrap those values with R.always
, via availableValues: R.always(availableValues)
, but I think it simpler to use a lambda with a parameter of "_
", which signifies that the parameter is unimportant: availableValues: _ => availableValues
. You could also write availableValues: () => available values
, but the underscore demonstrates the fact that the usual value is ignored.
Fixing this gets a function that works:
let findValues = x => {
if (R.has('values')(x)){
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
return R.evolve({
availableValues: _ => availableValues,
selectedValues: _ => selectedValues
}, x)
}
return x;
}
let arrObj = [{myValues: []}, {availableValues: [], myValues: [], selectedValues: [], values: [{a: "x", b: "1"}, {a: "y", b: "2"}]}];
console.log(R.map(findValues, arrObj))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
If you run this snippet here, you will also see an interesting point, that the resulting availableValues
property is a reference to the original values
one, not a separate copy of it. This helps show that Ramda is reusing what it can of your original data. You can also see this by noting that R.map(findValues, arrObj)[0] === arrObj[0] //=> true
.
I wrote my own versions of this, not starting from yours. Here is one working function:
const findValues = when(
has('values'),
x => evolve({
availableValues: _ => prop('values', x),
selectedValues: _ => pluck('a', prop('values', x))
}, x)
)
Note the use of when
, which captures the notion of "when this predicate is true for your value, use the following transformation; otherwise use your original value." We pass the predicate has('values')
, essentially the same as above. Our transformation is similar to yours, using evolve
, but it skips the temporary variables, at the minor cost of repeating the prop
call.
Something still bothers me about this version. Using those lambdas with "_
" is really a misuse of evolve
. I think this is cleaner:
const findValues = when(
has('values'),
x => merge(x, {
availableValues: prop('values', x),
selectedValues: pluck('a', prop('values', x))
})
)
Here we use merge
, which is a pure function version of Object.assign
, adding the parameters of the second object to those of the first, replacing when they conflict.
This is what I would choose.
Since I started writing this answer, Scott Christopher posted another one, essentially
const findValues = R.map(R.when(R.has('values'), R.applySpec({
myValues: R.prop('myValues'),
values: R.prop('values'),
availableValues: R.prop('values'),
selectedValues: R.o(R.pluck('a'), R.prop('values'))
})))
This is a great solution. It also is nicely point-free. If your input objects are fixed to those properties, then I would choose this over my merge
version. If your actual objects contain many more properties, or if there are dynamic properties that should be included in the output only if they are in the input, then I would choose my merge
solution. The point is that Scott's solution is cleaner for this specific structure, but mine scales better to more complex objects.
This is not a terribly complex transformation, but it is non-trivial. One simple way to build something like this is to build it up in very minor stages. I often do this in the Ramda REPL so I can see the results as I go.
My process looked something like this:
Make sure I properly transform only the correct values:
const findValues = when(
has('values'),
always('foo')
)
map(findValues, arrObj) //=> [{myValues: []}, "foo"]
Make sure my transformer function is given the proper values.
const findValues = when(
has('values'),
keys
)
map(findValues, arrObj)
//=> [{myValues: []}, ["myValues", "values", "availableValues", "selectedValues"]]
Here identity
would work just as well as keys
. Anything that demonstrates that the right object is passed would be fine.
Test that merge
will work properly here.
const findValues = when(
has('values'),
x => merge(x, {foo: 'bar'})
)
map(findValues, arrObj)
//=> [
// {myValues: []},
// {
// myValues: [],
// values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
// availableValues: [],
// selectedValues: [],
// foo: "bar"
// }
// ]
So I can merge two objects properly. Note that this keeps all the existing properties of my original object.
Now, actually transform the first value I want:
const findValues = when(
has('values'),
x => merge(x, {
availableValues: prop('values', x)
})
)
map(findValues, arrObj)
//=> [
// {myValues: []},
// {
// myValues: [],
// values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
// availableValues: [{a: "x", b: "1"}, {a: "y", b: "2"}],
// selectedValues: []
// }
// ]
This does what I want. So now add the other property:
const findValues = when(
has('values'),
x => merge(x, {
availableValues: prop('values', x),
selectedValues: pluck('a', prop('values', x))
})
)
map(findValues, arrObj)
//=> [
// {myValues: []},
// {
// myValues: [],
// values: [{a: "x", b: "1"}, a: "y", b: "2"}],
// availableValues: [{a: "x", b" "1"}, {a: "y", b: "2"}],
// selectedValues: ["x", "y"]
// }
// ]
And this finally gives the result I want.
This is a quick process. I can run and test very quickly in the REPL, iterating my solution until I get something that does what I want.
Upvotes: 6
Reputation: 6516
R.evolve
allows you to update the values of an object by passing them individually to their corresponding function in the supplied object, though it doesn't allow you to reference the values of other properties.
However, R.applySpec
similarly takes an object of functions and returns a new function that will pass its arguments to each of the functions supplied in the object to create a new object. This will allow you to reference other properties of your surrounding object.
We can also make use of R.when
to apply some transformation only when it satisfies a given predicate.
const input = [
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [],
"selectedValues": []
}
]
const fn = R.map(R.when(R.has('values'), R.applySpec({
myValues: R.prop('myValues'),
values: R.prop('values'),
availableValues: R.prop('values'),
selectedValues: R.o(R.pluck('a'), R.prop('values'))
})))
const expected = [
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"selectedValues": ["x", "y"]
}
]
console.log(R.equals(fn(input), expected))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Upvotes: 2