Batters
Batters

Reputation: 749

Access a property on an object from an array of keys

I want to write a helper function to unpack a specific object property from each object in an array of objects. Sometimes this property will be top level, other times it will be nested an arbitrary number of levels. So the crux of this question is: how can I access an object property based on an array of key names of variable length?

I'm hoping for something like:

const func = (arrOfObjects, ...keys) {
    return arrOfObjects.map(object => {
        return object[keys[0]][keys[1]] ... [keys[N]];
    })
}

with example behaviour:

const input = [
    {a: b: {c: 10}},
    {a: b: {c: 11}},
    {a: b: {c: 12}}
]

console.log(func(input, 'a', 'b', 'c'))
// [10, 11, 12]
console.log(func(input, 'a', 'b'))
// [{c: 10}, {c: 11}, {c : 12}]

I feel like there has to be a nice ES6 wizardry solution but as yet haven't found it so any help would be much appreciated!

Cheers,

P

Upvotes: 1

Views: 1512

Answers (3)

chitzui
chitzui

Reputation: 4098

Based on the answers you gave me to my questions and your example. It seems as if the order will of the input will always match the objects nesting. So here is my solution:

const func = (arrOfObjects, ...keys) => {
  return arrOfObjects.map(object => {
    let obj = object, integer = keys.length;
    for (let index = 0; index < integer; index++) {
      obj = obj[keys[index]];
      if(obj === undefined) break;
    }
    return obj;
  });
};

const input = [
  { a: { b: { c: 10 } } },
  { a: { b: { c: 11 } } },
  { a: { b: { c: 12 } } }
];

console.log(func(input, "a", "b", "c"));
// [10, 11, 12]
console.log(func(input, "a", "b"));
// [{c: 10}, {c: 11}, {c : 12}]

Unfortunately there is no such thing as the javascript magic you where expecting. Note: this code will not work when the order of the keys inside the object are nested at random depth. But for what you are trying to solve, this should work just fine. Also, I tried to preserve your initial code as good as possible

Upvotes: 1

VLAZ
VLAZ

Reputation: 29095

You can get a short and easy solution using Array#reduce

const input = [
    {a: { b: {c: 10}}},
    {a: { b: {c: 11}}},
    {a: { b: {c: 12}}}
]


console.log(func(input, ['a', 'b', 'c']))
// [10, 11, 12]
console.log(func(input, ['a', 'b']))
// [{c: 10}, {c: 11}, {c : 12}]

function func(input, props) {
  return input.map(x => exctractByProps(x, props));
}

function exctractByProps(obj, props) {
  return props.reduce( 
    (acc, prop) => typeof acc === 'object' && prop in acc ? acc[prop] : undefined, 
    obj 
  )
}

The main logic is to grab all the properties passed in and then try to get the value corresponding to obj[prop[0]][prop[1]][prop[2]]/* ... */[prop[n]]. If the object has an odd shape that doesn't match up with prop (for example, an input of {a: 1}, ['a', 'b'] or {d: {c: 1}}, ['a', 'b']) then the function returns undefined.

Upvotes: 2

Shubham Khatri
Shubham Khatri

Reputation: 282080

If you supply the accessor like [a, b, c[0], name], you can write a custom function which returns the value if found in a nested Object, otherwise returns undefined

 let obj = {
   a: 1,
   b: 2,
   c: {
     d: 1,
     e: [{
       f: 3,
       g: [{
         z: 45
       }]
     }]
   }
 }
function findKeyFromPattern(obj, patternArr) {
  let value = obj;
  for(let i = 0; i < patternArr.length; i++) {
    const arrmatch = patternArr[i].match(/(\w+)\[(\d+)\]/);
    if(arrmatch) { // pattern matches and array accessor syntax
      value = value[arrmatch[1]];
      if(typeof value === 'object' && value !== null) {
        value = value[arrmatch[2]];
      } else {
        return;
      }
    } else {
      if(value[patternArr[i]]) {
        value = value[patternArr[i]];
      } else {
        return;
      }
    }
  }
  return value;
}


console.log(findKeyFromPattern(obj, ['c', 'e[0]', 'g']));
console.log(findKeyFromPattern(obj, ['c', 'e[1]', 'g']))

Upvotes: 0

Related Questions