Jez
Jez

Reputation: 30071

Can I narrow this down in TypeScript?

I have a utility function to check whether a variable is not null or undefined, and I want TypeScript to narrow down the input variable if it passes the check, for example:

public init(input?: string): void {
    function isSpecified(input: any): boolean {
        return (typeof input !== "undefined") && (input !== null);
    }

    if (isSpecified(input)) {
        let copiedString: string = input; // <-- Error; input is still 'string | undefined'
    }
}

As you can see TS is not removing the possibility of the string being undefined even though the function makes that logically impossible. Is there a way I can get this function call to narrow down input inside the if block?

Upvotes: 2

Views: 1019

Answers (3)

Ryan Cavanaugh
Ryan Cavanaugh

Reputation: 221402

You can use a generic type guard function:

public init(input?: string): void {
    function isSpecified<T>(input: null | undefined | T): input is T {
        return (typeof input !== "undefined") && (input !== null);
    }

    if (isSpecified(input)) {
        let copiedString: string = input; // OK
    }
}

Note that if TypeScript narrows down a variable's union type through narrowing during an assignment, isSpecified will still not act as a type guard. For example, in the following code, TS will give an error:

someMethod = () => {
    const foo: undefined | null | number = undefined;
    if (isSpecified(foo)) {
        this.nonNullValue = foo; // Error
    }
};

In the above, foo being assigned as undefined narrows the variable type and the T in isSpecified is undefined, so TS still doesn't treat foo as definitely being specified. This can be worked around by using a type assertion (val as ...) to prevent narrowing:

someMethod = () => {
    //const foo = 123 as
    // ... or:
    const foo = undefined as
        undefined | null | number;
    if (isSpecified(foo)) {
        this.nonNullValue = foo;
    }
};

Upvotes: 6

Duncan
Duncan

Reputation: 95772

While the type guard function suggested in the other answers works well in many cases, in this case you have another much simpler option. Instead of checking for (typeof input !== "undefined") && (input !== null) just inline a check for input != null.

It's easy to forget that sometimes the type casting done by the double equal == and != can actually be useful:

function init(input?: string): void {
    if (input != null) {
        let copiedString: string = input; // <-- input is now 'string'
    }
}

In javascript or typescript the following are all true:

undefined == null
null == null
'' != null

Upvotes: 0

Tom Fenech
Tom Fenech

Reputation: 74705

Yes, you've basically just written a typeguard function without adding the typeguard.

Change:

function isSpecified(input: any): boolean

to:

function isSpecified(input: any): input is string

More generally, you can use a generic version of the same thing, as mentioned by Ryan:

function isSpecified<T>(input: null | undefined | T): input is T

Upvotes: 2

Related Questions