Reputation: 151
I am looping through a nested object. The return data is wrapped by two arrays. I understand why that is, but I do not understand how to get the desired data back.
const data = {
"foo": {
"bar": {
"id": "1",
"step": [{
"id": "33",
"copy": [{
"id": "1",
"text": "hello",
},
{
"id": "2",
"text": "whirl",
},
{
"id": "3",
"text": "whoa",
}
],
}]
}
}
}
pipe(
path(['foo', 'bar', 'step']),
map(step =>
step.copy.map(s => ({text: s.text}))
)
) (data)
the return data returns this:
[[{"text": "hello"}, {"text": "whirl"}, {"text": "whoa"}]]
I want to return this back:
[{"text": "hello"}, {"text": "whirl"}, {"text": "whoa"}]
Upvotes: 1
Views: 2308
Reputation: 50797
The simplest fix I see to your code is to replace the outer map
with chain
. You could alternatively follow it with flatten
or, better, unnest
, but chain
can be thought of (at least when applied to arrays; it's actually more general) as map
followed by unnest
.
That would do what you want. But I would suggest that if you're going to use point-free Ramda code for the rest of it, that you also replace your internal lambdas to get to something like this:
const transform = pipe(
path(['foo', 'bar', 'step']),
chain(prop('copy')),
project(['text'])
)
const data = {"foo": {"bar": {"id": "1", "step": [{"copy": [{"id": "1", "text": "hello"}, {"id": "2", "text": "whirl"}, {"id": "3", "text": "whoa"}], "id": "33"}]}}}
console.log(transform(data))
<script src="//bundle.run/[email protected]"></script>
<script>
const {pipe, path, chain, prop, project} = ramda
</script>
This looks good to me. But note that destructuring makes this much cleaner to do with raw JS than it used to be. This should also work:
const transform = ({foo: {bar: {step}}}) => step.flatMap(
({copy}) => copy.map(({text}) => ({text}))
)
const data = {"foo": {"bar": {"id": "1", "step": [{"copy": [{"id": "1", "text": "hello"}, {"id": "2", "text": "whirl"}, {"id": "3", "text": "whoa"}], "id": "33"}]}}}
console.log(transform(data))
In this case, the Ramda version reads more easily to me, but there's only a small difference.
Upvotes: 1
Reputation: 17190
One solution using Ramda.js to achieve what you need is to pipe a R.flatten after the R.map()
.
const data = {
"foo": {
"bar": {
"id": "1",
"step": [{
"id": "33",
"copy": [
{"id": "1", "text": "hello"},
{"id": "2", "text": "whirl"},
{"id": "3", "text": "whoa"}
],
}]
}
}
}
let res = R.pipe(
R.path(['foo', 'bar', 'step']),
R.map(step => step.copy.map(s => ({text: s.text}))),
R.flatten
) (data)
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Alternative, another solution coluld be replacing the outter R.map()
by a R.reduce():
const data = {
"foo": {
"bar": {
"id": "1",
"step": [{
"id": "33",
"copy": [
{"id": "1", "text": "hello"},
{"id": "2", "text": "whirl"},
{"id": "3", "text": "whoa"}
],
}]
}
}
}
let res = R.pipe(
R.path(['foo', 'bar', 'step']),
R.reduce((acc, curr) => acc.concat(curr.copy.map(({text}) => ({text}))), [])
) (data)
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Upvotes: 1
Reputation: 36574
The problem is that step
is itself an array
. You need to get its first item or pass 0
in arguments. Here is a pure js version of finding data from path. I used reduce()
const data = { "foo": { "bar": { "id": "1", "step": [{ "id": "33", "copy": [{ "id": "1", "text": "hello" }, { "id": "2", "text": "whirl" }, { "id": "3", "text": "whoa" } ] }] } } }
function getFromPath(obj, path) {
return path.reduce((ac, a) => ac[a] || {}, obj);
}
let res = getFromPath(data, ["foo","bar","step",0,"copy"]).map(({text}) => ({text}))
console.log(res)
Upvotes: 2
Reputation: 2148
Since copy contains an array, you need to specify you we're going to loop through that array. And you had the right idea, then change the value.
This
step.copy.map(s => ({text: s.text}))
Needs to be
data['foo']['bar']['step'][0]['copy'].map(s => ({text: s.text}))
or data.foo.bar.step[0].copy
data.foo.bar.step.map(v => {
return v.copy.map(v => {
return { text: v.text}
})
});
Example
const data = {
"foo": {
"bar": {
"id": "1",
"step": [{
"id": "33",
"copy": [{
"id": "1",
"text": "hello",
},
{
"id": "2",
"text": "whirl",
},
{
"id": "3",
"text": "whoa",
}
],
}]
}
}
}
// Output: [{"text": "hello"}, {"text": "whirl"}, {"text": "whoa"}]
let sorted = data['foo']['bar']['step'][0]['copy'].map(s => ({text: s.text}))
// let sorted = data.foo.bar.step[0].copy.map(s => ({text: s.text}))
console.log(sorted);
Then you could pipe
pipe(sorted)
Upvotes: 0
Reputation: 1509
The reason you get an array or arrays is because the step
value can be iterated. Removing the 2 layers of array implies you are restricting only to a single value of the step
array:
pipe(
path(['foo', 'bar', 'step', '0', 'copy']),
map(s => ({text: s.text}))
) (data)
Or
You want to concat all the step
values to the single outer array.
flatten(pipe(
path(['foo', 'bar', 'step']),
map(step =>
step.copy.map(s => ({text: s.text}))
),
) (data))
Upvotes: -2