2

Lets say I have an Array of Objects and each have an Array of objects inside them.

let nestedObjects = [{
  name: '1',
  values: [{id: 1}]
},
{
  name: '2',
  values: [{id: 2}]
}]

Now, I want to do something with each Object's values id. Sort of like this

let nestedObjects = [{
    name: '1',
    values: [{
      id: 1
    }]
  },
  {
    name: '2',
    values: [{
      id: 2
    }]
  }
];

let someArray = [];
nestedObjects.forEach(obj => {
  obj.values.forEach(value => {
    someArray.push(value.id * 2);
  });
});

console.log('Values', someArray);

I can sort of change this to use a reduce and possibly a map inside the reduce, but it still feels clunky and not really "functionally". Is there a different way to write it to make it shorter and easier to read?

I would like to get to something like:

let someArray = nestedObjects.map(obj => obj.values)
.map(value => value.id * 2)

console.log('VALUES', someArray);

Note: It is not really about the above code, but more about the FP way of thinking/coding.

4
  • Why don't you want a map inside a reduce? nestedObjects.reduce((p, o) => p.concat(o.values.map(v => v.id * 2)), []) looks tidy enough. Commented Nov 1, 2017 at 14:20
  • This is similar to the solution I was going to present Commented Nov 1, 2017 at 14:34
  • @Ozan looks good enough, true. Just curious as to a potentially better way of writing it, and why. Not so much about "good enough" or getting the code to work, but more of a FP discovery mission for myself. Yes, there are resources out there about FP in JS, but sometimes it's hard to apply it to a specific situation, like this one (for me at least). Thanks for the responses though :) Commented Nov 1, 2017 at 14:37
  • You need a reduce inside a map, because the latter have to return the same context, that is an Array in your example: nestedObjects.map(o => o.values.reduce((acc, p) => acc + p.id * 2, 0)). Commented Nov 1, 2017 at 15:31

2 Answers 2

3

Use Array#concat and spread to flatten the subarrays to a single array. Then iterate the results with the 2nd map:

const nestedObjects = [{"name":"1","values":[{"id":1}]},{"name":"2","values":[{"id":2}]}];

const someArray = [].concat(...nestedObjects.map(obj => obj.values))
.map(value => value.id * 2);

console.log('VALUES', someArray);

The combination of getting an array from a property, and flattening the result is called flatMap, so you can extract the code to a method:

const flatMap = (cb, arr = []) => [].concat(...arr.map(obj => cb(obj)));

Important note: the spread argument is not stack-safe, this flatMap method will fail for a huge amount of data.

Demo:

const nestedObjects = [{"name":"1","values":[{"id":1}]},{"name":"2","values":[{"id":2}]}];

const flatMap = (cb, arr = []) => [].concat(...arr.map(obj => cb(obj)));

const someArray = flatMap(({values}) => values, nestedObjects).map(value => value.id * 2);

console.log('VALUES', someArray);

btw - Native JS Array.prototype.flatMap is a stage2 proposal, and it should probably make it to the language in ES2018 or ES2019.

Sign up to request clarification or add additional context in comments.

Comments

1

Your initial instinct to use reduce/map is good – your worry that it's not "functional" is unfounded; it's functional – it uses functions, produces no side effects, and will always produce the same result given the same initial input

const data0 =
  [ { name: 1, values: [ { id: 1 } ] },
    { name: 2, values: [ { id: 2 } ] } ]

const data1 =
  data0.reduce ((acc, { values = [] }) =>
    acc.concat (values.map (v => v.id)), [])
  
console.log (data1)
  

You can drop the default = [] if you're guaranteed that each item in data0 will have a values property with an array value


This reducing/flattening of an array is common, and can be abstracted for easier reuse – another user provided something similar but it included a fatal flaw which could result overflowing the stack – this implementation avoids that problem

const concatMap = (f, xs) =>
  xs.reduce ((acc, x) =>
    acc.concat (f (x)), [])

const data0 =
  [ { name: 1, values: [ { id: 1 } ] },
    { name: 2, values: [ { id: 2 } ] } ]

const data1 =
  concatMap (({ values = [] }) =>
    values.map (v => v.id), data0)

console.log (data1)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.