Ramda - how to get multiple properties from array

Viewed 51

I am trying to get two properties from an array of objects which looks like this:

const nodes = [
 {
    "id": "0",
    "outgoingNodeIds": ['1', '2'],
    "outgoingVirtualNodes": [
        {
            "virtualNodeId": "5",
        },
        {
            "virtualNodeId": "10",
        }
    ],
 }
]

I need to get outgoingNodeIds which is a simple array of strings and outgoingVirtualNodes which is an array of objects and I need to get virtualNodeId from it.

There is no problem with getting only one property at the time and then concat two arrays (example in ramda):

const prop1 = R.pipe(
    R.pluck('outgoingNodeIds'),
    R.flatten
)(nodes)

const prop2 = R.pipe(
    R.pluck('outgoingVirtualNodes'),
    R.map(R.pluck('virtualNodeId')),
    R.flatten
)(nodes)

R.concat(prop1, prop2)

But I would like to combine this into one pipe if possible. I know how to get two properties at once but I don't know how to map the second one to get virtualNodeId.

Any help would be appreciated. Thank you

2 Answers

Use R.ap to apply a list of functions to the array - each function produces a new array of values from all object objects in the original array, and then flatten to a single array. The end result would be an array of all outgoingNodeIds from all objects, and then the virtualNodeId from all objects:

const { pipe, ap, prop, pluck, flatten } = R

const fn = pipe(
  ap([
    prop('outgoingNodeIds'),
    pipe(prop('outgoingVirtualNodes'), pluck('virtualNodeId'))
  ]),
  flatten,
)

// Note that are two objects - a and b
const nodes = [{"id":"0","outgoingNodeIds":["1a","2a"],"outgoingVirtualNodes":[{"virtualNodeId":"5a"},{"virtualNodeId":"10a"}]},{"id":"1","outgoingNodeIds":["3b","4b"],"outgoingVirtualNodes":[{"virtualNodeId":"15b"},{"virtualNodeId":"20b"}]}]

const result = fn(nodes)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

If you want the outgoingNodeIds and then virtualNodeId of each object together replace R.ap with R.map and R.juxt. The R.juxt apply a list of functions to a list of parameters (a single object in this case).

const { pipe, map, juxt, prop, pluck, flatten } = R

const fn = pipe(
  map(juxt([
    prop('outgoingNodeIds'),
    pipe(prop('outgoingVirtualNodes'), pluck('virtualNodeId'))
  ])),
  flatten,
)

// Note that are two objects - a and b
const nodes = [{"id":"0","outgoingNodeIds":["1a","2a"],"outgoingVirtualNodes":[{"virtualNodeId":"5a"},{"virtualNodeId":"10a"}]},{"id":"1","outgoingNodeIds":["3b","4b"],"outgoingVirtualNodes":[{"virtualNodeId":"15b"},{"virtualNodeId":"20b"}]}]

const result = fn(nodes)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Since we're dealing with a single object on each iteration of R.map, you can replace R.juxt with R.applySpec:

const { pipe, map, applySpec, prop, pluck, flatten } = R

const fn = pipe(
  map(applySpec([
    prop('outgoingNodeIds'),
    pipe(prop('outgoingVirtualNodes'), pluck('virtualNodeId'))
  ])),
  flatten,
)

// Note that are two objects - a and b
const nodes = [{"id":"0","outgoingNodeIds":["1a","2a"],"outgoingVirtualNodes":[{"virtualNodeId":"5a"},{"virtualNodeId":"10a"}]},{"id":"1","outgoingNodeIds":["3b","4b"],"outgoingVirtualNodes":[{"virtualNodeId":"15b"},{"virtualNodeId":"20b"}]}]

const result = fn(nodes)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Ori Drori has already given several great Ramda versions. But let's not forget how simple this can be in modern JS:

const extractNodeIds = (nodes) => nodes .flatMap ((node) => [
  ... (node .outgoingNodeIds),
  ... (node .outgoingVirtualNodes .map (vn => vn .virtualNodeId))
])

const nodes = [{id: '0', outgoingNodeIds: ['1', '2'], outgoingVirtualNodes: [{virtualNodeId: '5'}, {virtualNodeId: '10'}]}]

console .log (extractNodeIds (nodes))

Ramda was designed and mostly built in the days before ES6 was ubiquitous. But these days, tools like flatMap and the array ...spread syntax make many problems that Ramda solved so nicely almost as nice in plain JS. It's not that I don't love the tool -- I'm a Ramda founder and a big fan -- but it no longer seems as necessary as it once did.

Related