5

I'm looking for some good strategies for avoiding errors in JavaScript when using dot notation to call the children of children in objects that may or may not exist.

At the bottom of the code snippet below is an example of a solution that works, but is inelegant (at best).

It would be great to see some native JavaScript solutions or even external libraries that can help avoid this kind of error.

const object1 = {
  foo: {
    bar: {
      baz: 'payload'
    }
  }
};


const object2 = {};

const array = [object1, object2];

// this will fail on object2 because obj.foo is undefined
array.forEach(obj => {
    if (obj.foo.bar.baz) {
      console.log(obj.foo.bar.baz);
     } else {
      console.log('undefined');
     }
  } 
);

// this will work, but it's horrible to write all those nested if statements.
array.forEach(obj => {
    if (obj) {
      if (obj.foo) {
        if (obj.foo.bar) {
          if (obj.foo.bar.baz) {
          console.log(obj.foo.bar.baz);
          }
        }
      }
    } else {
      console.log('undefinded');
    }
  }
);
Ivan
  • 34,531
  • 8
  • 55
  • 100
Gerard
  • 768
  • 5
  • 20
  • Why not catch it and then do whatever behavior you desire instead of having if statements for everything? – chevybow Jun 22 '18 at 21:07
  • @chevybow My example might have been too general. I write a lot of JSX, so I use the ternary operator frequently. Try/Catch isn't an option in that case, so I was looking for a functional approach. – Gerard Jun 22 '18 at 21:10
  • 2
    if `console.log(obj?.foo?.bar?.baz)` looks more elegant, see https://github.com/tc39/proposal-optional-chaining – Aprillion Jun 22 '18 at 21:11
  • @Aprillion That's a pretty good solution! I think stage 1 is too far away for my team to be comfortable using a Babel plugin though. Do you know of a functional approach? – Gerard Jun 22 '18 at 21:16
  • 1
    [This](https://medium.com/javascript-inside/safely-accessing-deeply-nested-values-in-javascript-99bf72a0855a) might interest you – Ivan Jun 22 '18 at 21:18

4 Answers4

9

Lodash already did it for us: https://lodash.com/docs#get

const object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// => 3

_.get(object, ['a', '0', 'b', 'c']);
// => 3

_.get(object, 'a.b.c', 'default');
// => 'default'
Jordan Enev
  • 16,904
  • 3
  • 42
  • 67
6

No sure if that's enough of an improvement but you can use a single if statement with the following condition:

(obj && obj.foo && obj.foo.bar && obj.foo.bar.baz)

This will check if obj.foo.bar.baz exists.

const array=[{foo:{bar:{baz:'payload'}}},{}]

array.forEach(obj => {
  if (obj && obj.foo && obj.foo.bar && obj.foo.bar.baz) {
    console.log(obj.foo.bar.baz);
  } else {
    console.log('undefined');
  }
});
Ivan
  • 34,531
  • 8
  • 55
  • 100
4

You could chain all checks with logical AND &&.

const
    object1 = { foo: { bar: { baz: 'payload' } } },
    object2 = {},
    array = [object1, object2];

array.forEach(obj => {
    if (obj && obj.foo && obj.foo.bar && obj.foo.bar.baz) {
        console.log(obj.foo.bar.baz);
    } else {
        console.log('undefined');
    }
});

For an automatic check, you could take an array of keys and return either the value or undefined.

const
    getValue = (object, keys) => keys.reduce((o, k) => (o || {})[k], object),
    object1 = { foo: { bar: { baz: 'payload' } } },
    object2 = {},
    array = [object1, object2];

array.forEach(obj => console.log(getValue(obj, ['foo', 'bar', 'baz'])));
Bourbia Brahim
  • 14,459
  • 4
  • 39
  • 52
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

Just to share my two cents:

Some time ago I made a function that allowed to safely access deep properties in javascript using proxies:

// Here is where the magic happens
function optional(obj, evalFunc, def) {

  // Our proxy handler
  const handler = {
    // Intercept all property access
    get: function(target, prop, receiver) {
      const res = Reflect.get(...arguments);

      // If our response is an object then wrap it in a proxy else just return
      return typeof res === "object" ? proxify(res) : res != null ? res : def;
    }
  };

  const proxify = target => {
    return new Proxy(target, handler);
  };

  // Call function with our proxified object
  return evalFunc(proxify(obj, handler));
}

const obj = {
  items: [{
    hello: "Hello"
  }]
};

console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, {
  a: 1
})); // => { a: 1 }

Also, I wrote an article on this for further reference.

J. Pichardo
  • 3,077
  • 21
  • 37