8

I have a nested object which may have stuff missing:

const unreliableObject = {
    id: 10,
    nestedObject: { // may be missing
        id: 11 // may also be missing
    }
}

Now say I want to get the inner id. I can do the following

const { nestedObject: { id: key = -1 } = {key: -1} } = unreliableObject;
console.log(key);

Is there a better way? Something where I'm not defining {key: -1} twice, nor using an empty object (we have lint rules in place), and I still want to default key in that case.

AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
  • 6
    `var { nestedObject: { id: key = -1 } = {}} = unreliableObject;` or maybe [How to avoid 'cannot read property of undefined' errors](https://stackoverflow.com/questions/14782232/how-to-avoid-cannot-read-property-of-undefined-errors/42349521). – str Jan 30 '19 at 13:12
  • That could work, but we are using a lint rule to prevent empty objects. I could use lodash's `get` and use the defaults. – AncientSwordRage Jan 30 '19 at 13:23
  • 1
    Possible duplicate of [Nested Object destructuring](https://stackoverflow.com/questions/41171421/nested-object-destructuring) – adiga Jan 30 '19 at 13:48
  • 5
    @Pureferret Maybe you should reconsider that linter rule when it makes no sense – Bergi Jan 30 '19 at 14:02
  • @Bergi I will have a word with the powers that be.... – AncientSwordRage Jan 30 '19 at 14:02
  • @adiga except I'm doing that already – AncientSwordRage Jan 30 '19 at 14:03
  • 2
    1) Not really. The dupe has an answer which is same as what @str suggested with an empty object `{}` as default 2) It's a way of linking it to the dupe to help future visitors. – adiga Jan 30 '19 at 14:05
  • Possible duplicate of [How to avoid 'cannot read property of undefined' errors?](https://stackoverflow.com/questions/14782232/how-to-avoid-cannot-read-property-of-undefined-errors) – str Jan 30 '19 at 14:11
  • @adiga I'm doing as close as I can. I'll edit to explain that. – AncientSwordRage Jan 30 '19 at 14:33

1 Answers1

1

If you're willing to accept a path notation similar to Lodash's get and the use of a Proxy object then you can turn p['x.y.z'] into a safe p.x.y.z operation:

const {'x.y.z': a = 42} = path({});
const {'x.y.z': b = 42} = path({x: {}});
const {'x.y.z': c = 42} = path({x: {z: {}}});
const {'x.y.z': d = 42} = path({x: {y: {z: 999}}});

console.log(a, b, c, d);
<script>
const path = obj =>
  new Proxy(obj, {
    get(o, k) {
      const [head, ...tail] = k.split('.'); 
      return tail.length === 0 ?
        o[head] :
        path(o[head] != null ? o[head] : {})[tail.join('.')];
    }
  });
</script>

If you're willing to not use destructuring and accept even more Proxy magic, then we could do other crazy things like turning properties into functions!

const p = path(/* some object */);
p.x.y.z(42);
// either the value at 'x.y.z' or 42

const a = path({});
const b = path({x: {}});
const c = path({x: {z: {}}});
const d = path({x: {y: {z: 999}}});

console.log(a.x.y.z(42),
            b.x.y.z(42),
            c.x.y.z(42),
            d.x.y.z(42));
<script>
const path = obj =>
  new Proxy(obj, {
    get: (o, k) =>
      o[k] == null
        ? path(x => x)
        : typeof o[k] === 'object'
          ? path(o[k])
          : () => o[k] });
</script>

This is probably overengineered but I wanted to toy with Proxy anyway ;) Perhaps this helps though.

customcommander
  • 17,580
  • 5
  • 58
  • 84