0

I have a utility function like this, mainly to get around the annoyance that typedef null is 'object'.

export function isObject(a: any): a is object {
  return a && (typeof a === 'function' || typeof a === 'object');
}

Instead of declaring the return type of this function boolean, you can use a is object, which is essentially a boolean too, but it assists type inference so that TypeScript knows that, if this function is true, a can be treated as an object without further type checking or casting.

I'd like to additionally declare that a is not null, but I can't find syntax that works.

export function isObject(a: any): a is NonNullable<object> {
  return a && (typeof a === 'function' || typeof a === 'object');
}

This at least has the advantage that it parses, but it doesn't help TypeScript infer that a is non-null.

I've tried other things that don't parse, like a is object & !null or a is object & null: never -- none of things work.

Is there a way to declare what I want to declare?

enter image description here

kshetline
  • 12,547
  • 4
  • 37
  • 73
  • 1
    Typescript has a [strict null check](https://basarat.gitbook.io/typescript/intro/strictnullchecks) option. You don't need to try to create a user defined type guard for it. – Jared Smith Jan 07 '21 at 03:05
  • Why exactly do you want an `a is object` type guard? – kaya3 Jan 07 '21 at 03:07
  • Because the type guard assists in type inference. A better example might be with `a is number`. If you have an a function `foo(a: number | string)` and inside you have a test `if (isNumber(a)) b += a` TypeScript knows to allow this math without objection, without having to do `b += a as number`, etc., as long as `isNumber()` returns `a is number`. – kshetline Jan 07 '21 at 03:12
  • Sure, I'm just wondering why you would want `object` specifically as a type guard, since there is not much you can safely do with just a plain `object`. – kaya3 Jan 07 '21 at 03:13
  • It's in a case where TypeScript knows that if the value is an object, then it's also a specific interface. I noticed code highlighting after my `isObject` test that meant "be careful, this variable might be null", when I knew it really couldn't be null. – kshetline Jan 07 '21 at 03:16
  • @Jared, because I'd like the function declaration to do the whole job by itself, regardless of whether strict null checks are in effect. – kshetline Jan 07 '21 at 03:18
  • 1
    @kshetline then you probably just want to turn on strict mode and strictNullChecks? – Evert Jan 07 '21 at 03:18
  • @Evert, as I said to Jared, I'd like the declaration to do the whole job, regardless of strictNullChecks being active or not. – kshetline Jan 07 '21 at 03:19
  • 2
    Please post the code from your image as actual text (see [this](//meta.stackoverflow.com/q/285551) for why) and ideally make sure the code constitutes a [mcve]. – jcalz Jan 07 '21 at 03:40

3 Answers3

4

With --strictNullChecks turned off, there's no way to have the compiler help you guard against null or undefined, since any possibly-null value will be treated the same as if it were non-null:

function foo(x: {a: string} | null) {
  x.a.toUpperCase(); // error if and only if --strictNullChecks is enabled
}

So the only way to proceed is to turn --strictNullChecks on so that the compiler can actually tell the difference between null and non-null values. I will assume from here on out that this is the case.


The type guard function you are using is already guarding against null by returning a is object. The object type in TypeScript (which starts with a lowercase o, not to be confused with the Object type) refers only to non-null and non-undefined object types:

const bar: object = null; // error!
//    ~~~ <-- null is not assignable to object

Please note the difference between the string "object" returned by the JavaScript typeof operator at runtime and the TypeScript type object. At runtime, typeof null returns "object", whereas in the type system typeof null is null, not object. It's potentially confusing because identical or similar identifiers can mean quite different things depending on whether you are looking at values or types. You can read one of my other diatribes answers for more details about that.


Again, if --strictNullChecks is enabled, the object type already excludes null. Therefore you should be able to just use your original type guard function returning a is object. Here's an example:

function baz(x: {a: string} | null) {
  if (isObject(x)) {
    x.a.toUpperCase(); // no error now
  } else {
    const y: null = x; // also no error
  }
}

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
2

Here's a way using Exclude with a generic type parameter:

function nonNullObject<T>(x: T): x is Exclude<T, null | undefined> & object {
    return x && (typeof x === 'object' || typeof x === 'function');
}

Example usage: inside the if statement, number and null are eliminated from the type of x by control-flow type narrowing.

declare let x: Record<string, number> | number | null;

if(nonNullObject(x)) {
    x // Record<string, number>
}

Playground link

kaya3
  • 47,440
  • 4
  • 68
  • 97
  • Thanks. This looks good, and is probably correct, but maybe IntelliJ's editor isn't smart enough to recognize the non-nullness, because the warning remains. (I added a picture to my post.) Oh, as if you think this is a fair question I had, would you do me the favor of canceling the downvote my question got? :-) – kshetline Jan 07 '21 at 03:29
  • I just discovered the shorter syntex `Exclude` works as well. – kshetline Jan 07 '21 at 03:42
  • `object` already excludes `null`. And since `object` is not a union type, `Exclude` is just `object`. – jcalz Jan 07 '21 at 04:01
0

I finally found notation that does what I want:

export function isObject(a: any): a is Record<string | number | symbol, any> {
  return a && (typeof a === 'function' || typeof a === 'object');
}

Not only does this express the non-nullness of a when the function returns true, but it allows me to access arbitrary properties on a without getting errors that the properties don't exist, which happens with a is object as the function return type.

if (isObject(a)) {
  console.log(a.foo); // No error that property `foo` doesn't exist on `a`
}
kshetline
  • 12,547
  • 4
  • 37
  • 73