tinker
tinker

Reputation: 3424

How to get specific properties of array of objects in JS?

I have the following code and test data:

const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) => {
        return (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj;
    });
}

const obj = 
 [
  {
      a: 1,
      c: [
        {
          d: 1,
          e: 'string',
          f: [
            {
              value: 0,
            },
            {
              value: 1,
            }
          ],
        },
      ],
  },
    {
      a: 2,
      c: [
        {
          d: 2,
          e: 'string',
          f: [
            {
              value: 3,
            },
            {
              value: 4,
            }
          ],
        },
      ],
  },
 ];

console.log(obj);
const fs = obj.map(o => getNestedObject(o, ['c', 'f']));
console.log(fs);

What I want to do is given the array of objects shown below, I want to get only the property called f from every object in the array. So, basically end result should be array of f values of every object. Since 'f' is an array, I would highly appreciate the end result to be just one array with elements from all 'f' properties, so kind of every of these 'f' to be spread out, so I have one array. My above getNestedObject function does not seem to work, as when the console.log statement below returns the whole object. Any ideas how to do this in JS?

So basically the end result should be:

[{ value: 0 }, { value: 1 }, { value: 3 }, {value: 4 }]

Upvotes: 4

Views: 250

Answers (4)

VLAZ
VLAZ

Reputation: 28970

You can recursively traverse any objects and arrays to fetch a given property. This works at any depth and doesn't care about the structure of the objects:

const obj=[{a:1,c:[{d:1,e:"string",f:[{value:0},{value:1}]}]},{a:2,c:[{d:2,e:"string",f:[{value:3},{value:4}]}]}];

//curried function to grab a property by name off some object or array
function grab(prop) {
  //naming the inner function means it can be called recursively by name
  return function recursiveGrab(target) {
    if (Array.isArray(target)) {
      const arrayResult = target
          .filter(x => typeof x === "object") //remove non-objects (and non-arrays)
          .filter(Boolean) //remove null
          .map(recursiveGrab); //recursively call for the remaining objects 
      return flatten(arrayResult); //return a single dimensional array
    }
    
    //an object has the property - return it
    if (prop in target) {
      return target[prop];
    }
    
    //object doesn't have the property - check all values
    return recursiveGrab(Object.values(target));
  }
}

//small helper function. It's separated only to keep the logic for the traversal clear
function flatten(arr) {
  return arr.reduce((acc, curr) => acc.concat(curr), [])
}

const grabF = grab('f');

console.log(grabF(obj));

Upvotes: 1

Mark
Mark

Reputation: 92440

You can combine reduce() with map(). Basically reduce your main array into an flattened array of all the c.f items. This checks for the c property just in case the object doesn't have it:

const obj = [{a: 1,c: [{d: 1,e: 'string',f: [{value: 0,},{value: 1,}],},],},{a: 2,c: [{d: 2,e: 'string',f: [{value: 3,},{value: 4,}],},],},];

let Fs = obj.reduce((arr, item) => 
    item.c
    ? arr.concat(...item.c.map(itemc => itemc.f ))  // concat for flattened rather than nested arrays
    : arr
    , []);

console.log(Fs)

Upvotes: 4

ggorlen
ggorlen

Reputation: 56875

Here's a fast iterative solution that won't overflow the stack, makes no assumptions about target result values being arrays (only spreads if they are) and doesn't hard-code child key names (it'll explore any values that are arrays).

This can also work if the target has children matching the key that you'd like to include in the search (swap else if with if).

const get = (data, target) => {
  const result = [];
  const stack = [data];
  
  while (stack.length) {
    const curr = stack.pop();
    
    for (const o of curr) {
      for (const k in o) {
        if (k === target) {
          if (Array.isArray(o[k])) {
            result.push(...o[k]);
          }
          else {
            result.push(o[k]);
          }
        }
        else if (Array.isArray(o[k])) {
          stack.push(o[k]);
        }
      }
    }
  }
  
  return result;
};
 
const obj = 
[
  {
      a: 1,
      c: [
        {
          d: 1,
          e: 'string',
          f: [
            {
              value: 0,
            },
            {
              value: 1,
            }
          ],
        },
      ],
  },
    {
      a: 2,
      c: [
        {
          d: 2,
          e: 'string',
          f: [
            {
              value: 3,
            },
            {
              value: 4,
            }
          ],
        },
      ],
  },
 ];
 
 console.log(get(obj, "f"));

Upvotes: 1

Prasanna
Prasanna

Reputation: 4636

I did not notice that f was always inside c. I have this recursive and dirty looking solution that works with f being inside any of the fields

        const objArray = [
          {
              a: 1,
              c: [
                {
                  d: 1,
                  e: 'string',
                  f: [
                    {
                      value: 0,
                    },
                    {
                      value: 1,
                    }
                  ],
                },
              ],
             d: [
                {
                  d: 1,
                  e: 'string',
                  f: [
                    {
                      value: 'd',
                    },
                    {
                      value: 'd1',
                    }
                  ],
                },
              ],
          },
            {
              a: 2,
              c: [
                {
                  d: 2,
                  e: 'string',
                  f: [
                    {
                      value: 3,
                    },
                    {
                      value: 4,
                    }
                  ],
                },
              ],
              e: [
                {
                  d: 1,
                  e: 'string',
                  f: [
                    {
                      value: 'e',
                    },
                    {
                      value: 'e1',
                    }
                  ],
                },
              ],
            }
        ]
         
         
         const getFObject = (obj) => {
           let fObj = [];
           Object.keys(obj).some(key => {
             if (key === 'f') {
               fObj = obj[key];
               return true;
             }
             if (Array.isArray(obj[key])) {
               obj[key].forEach(nestedObj => {
                 fObj = fObj.concat(getFObject(nestedObj))
               });
             }
             return false;
           });
           return fObj;
         }
         
         const newArray = objArray.reduce((acc, obj) => {
           return acc.concat(getFObject(obj))
         }, []);
         console.log(newArray)

Upvotes: 0

Related Questions