2

I have the following function that performs a simple type assertion, checking that a variable is not undefined or null.

const isDefinedAndNotNull = <T>(val: T | null | undefined): val is NonNullable<T> =>
    (val !== null) && (typeof val !== "undefined");

However, I have an issue when using this function to assert the presence of a nested object using optional chaining. The below code will cause Typescript to not compile:

type MaybeNullThing = {
    myProp?: {
        coolThing?: string
    }
}

const thing: MaybeNullThing = getThing();

if (isDefinedAndNotNull(thing?.myProp?.coolThing)) {
    console.log(thing.myProp.coolThing); // error: thing.myProp could be undefined
}

However, Typescript has no problem with the following:

if (thing?.myProp?.coolThing) {
    console.log(thing.myProp.coolThing); // defined here
}

My question is essentially how to get the type assertion to assert that both the nested object and the target object are defined, if this is possible. Thanks!

Link to a TS Playground containing the above code

James Paterson
  • 2,652
  • 3
  • 27
  • 40
  • 1
    Could you maybe use a callback variant instead of an if? – Keith Aug 31 '22 at 12:30
  • I like this idea: https://www.typescriptlang.org/play?#code/MYewdgzgLgBAlgMwAoCcCmE1lgXhgHgBUAaAPgAoA3AQwBsAuGQmAHxjAFdbbWYOwAJmgRwwaAcXgIAIsNHjGVOo0IBKGDlIxKIOANWMdejVoDeAWABQMKTHJKeAQhx5O3dQDIPdqAE8ADmggCNp0MM54AET8QiJiApGq6hbWNmmIsnHiDqoA3FY2AL5WxZZWfoEwALLUvgBGaAByXLSEABaiAOYaMClpMAC2vqgg-gD8jH39aaAgrR1gnRMw0ChdBWmlpVagkLBQC52MNfVNLe1dPVNDI-6TGzaz812MkcDUmAC0oLQgHCi0DAQT6ddBYSIbLb5MqWRCoDBYKDkA5dMYAOhuKFG6KeF0WxA2ORMMF2EDmaDRv06OSseSAA but maybe an unwieldy API – James Paterson Aug 31 '22 at 12:39
  • Yeah, it's very similar to what I just knocked up :), you could also get rid of the brackets `val => console.log(val)`. If you think about it, it's not that unwieldy even React use this with there `useEffect`, there just not passing any value.. – Keith Aug 31 '22 at 12:42
  • Actually just extending the idea a little bit, you could maybe pass the props as an array in case you wanted to check for multiple, and use array destructuring in the callback. I think that would be pretty neat & tidy. – Keith Aug 31 '22 at 12:47

1 Answers1

1

The type assertion only really covers the deepest level; the property you're passing to the assertion.
Your condition only confirms coolThing is defined and not null.

To cover myProp, you'd need to explicitly check that as well:

if (isDefinedAndNotNull(thing?.myProp) && isDefinedAndNotNull(thing?.myProp?.coolThing)) {
  console.log(thing.myProp.coolThing);
}

TS is naïve like that, and I honestly don't know of a way around it.

Cerbrus
  • 70,800
  • 18
  • 132
  • 147