1

I have a JavaScript object array with the following structure:

somedata = {
  foo: {
    bar: [
      {
        baz: [
          {
            someprop: 'a'
          },
          {
            someprop: 'b'
          },
          {
            someprop: 'c'
          }
        ]
      },
      {
        baz: [
          {
            someprop: 'd'
          },
          {
            someprop: 'e'
          },
          {
            someprop: 'f'
          }
        ]
      }
    ]
  }
}

I want to extract someprop field from this JavaScript object as an array ['a', 'b', 'c', 'd', 'e', 'f']

currently, this is my code logic to extract someprop field as an array:

const result = []
somedata.foo.bar.forEach(x => {
  x.baz.forEach(y => {
    result.push(y.someprop)
  })
})
console.log(result) // prints ["a", "b", "c", "d", "e", "f"]

i tried to make the code more reusable by creating a function:

function extractToArray(data, arr, prop) {
  let result = []
  data.forEach(x => {
    x[arr].forEach(y => {
      result.push(y[prop])
    })
  })
  return result;
}
console.log(extractToArray(somedata.foo.bar, 'baz', 'someprop'))

But is there a more concise, elegant, cleaner way to achieve this?


Note: possible duplicate covers an array of objects, but this is regarding an array of objects of an array of objects (so a simple map solution won't work).

kimbaudi
  • 13,655
  • 9
  • 62
  • 74
  • You say a `map` solution won't work, but it will if you specifically point at the array reference, which is what you're doing currently anyway. – zfrisch Jul 15 '19 at 18:53
  • is the object result from a JSON string ? – Slai Jul 15 '19 at 18:56
  • i meant to say "a simple `map` solution" won't work. here is a not-so-simple (imho) map solution that would work: `somedata.foo.bar.map(i => i.baz).map(i => i.map(j => j.someprop)).flat()` – kimbaudi Jul 15 '19 at 18:56
  • @Slai no it is a javascript object, not json – kimbaudi Jul 15 '19 at 18:58
  • @kimbaudi Your solution in your most recent comment seems very elegant to me. For the nested structure that you have, you can't really simplify any further. – Code-Apprentice Jul 15 '19 at 19:00

4 Answers4

2

You could create recursive function that will find your prop on any level and return array as a result.

const somedata = {"foo":{"bar":[{"baz":[{"someprop":"a"},{"someprop":"b"},{"someprop":"c"}]},{"baz":[{"someprop":"d"},{"someprop":"e"},{"someprop":"f"}]}]}}

function get(data, prop) {
  const result = [];
  for (let i in data) {
    if (i == prop) result.push(data[prop]);
    if (typeof data[i] == 'object') result.push(...get(data[i], prop))
  }
  return result;
}

console.log(get(somedata, 'someprop'))
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
  • 1
    For the object check, I'd advise looking at the constructor instead of doing `typeof`. A.e. `data[i].constructor.name === "Object"` the reason why is because many things(like Arrays) return truthy as an object if you're only looking at `typeof`. – zfrisch Jul 15 '19 at 18:58
2

You can use flatMap for that:

const somedata = {foo:{bar:[{baz:[{someprop:"a"},{someprop:"b"},{someprop:"c"}]},{baz:[{someprop:"d"},{someprop:"e"},{someprop:"f"}]}]}};
const result = somedata.foo.bar.flatMap(({baz}) => baz.map(({someprop}) => someprop));
console.log(result);

Note that not every current browser supports this yet, so you might want to use a polyfill.

str
  • 42,689
  • 17
  • 109
  • 127
  • awesome! this was the solution i was looking for (specifically using `flatMap`). also +1 for destructuring assignment. This is basically the same solution I ended up with: `somedata.foo.bar.flatMap(i => i.baz).flatMap(i => i.someprop)` – kimbaudi Jul 15 '19 at 19:03
1

A recursive function that does it in one functional expression:

const extractToArray = (data, prop) => Object(data) !== data ? [] 
    : Object.values(data).flatMap(v => extractToArray(v, prop)) 
            .concat(prop in data ? data[prop] : []);

var somedata = {foo: {bar: [{baz: [{someprop: 'a'},{someprop: 'b'},{someprop: 'c'}]},{baz: [{someprop: 'd'},{someprop: 'e'},{someprop: 'f'}]}]}}
console.log(extractToArray(somedata, "someprop"));

This is reusable in the sense that it also works when the property is not always present, or not always at the same depth within the data structure.

trincot
  • 317,000
  • 35
  • 244
  • 286
1

For others with similar question, I am adding a more generic (but possibly a bit less efficient) alternative using the JSON.parse reviver parameter

var arr = [], obj = {foo:{bar:[{baz:[{someprop:"a"},{someprop:"b"},{someprop:"c"}]},{baz:[{someprop:"d"},{someprop:"e"},{someprop:"f"}]}]}}

JSON.parse(JSON.stringify(obj), (k, v) => k === 'someprop' && arr.push(v))

console.log(arr)
Slai
  • 22,144
  • 5
  • 45
  • 53