olafsadventures
olafsadventures

Reputation: 151

How do I remove an outer array?

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

Answers (5)

Scott Sauyet
Scott Sauyet

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

Shidersz
Shidersz

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

Maheer Ali
Maheer Ali

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

ABC
ABC

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

varun agarwal
varun agarwal

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

Related Questions