Typescript - using optional type predicates

I'm looking to create a custom type predicate function that can accurately determine if a number is real and tighten the type as well:

function isRealNumber(input: number | undefined | null): input is number {
    return input !== undefined && input !== null && Number.isFinite(input);
}

However, there are scenarios where this approach leads to incorrect types when negated, for instance:

const myNumber: number | null = NaN as any;
if (isRealNumber(myNumber)) {
    const b = myNumber;  // correct output: b is number
} else {
    const b = myNumber;  // expected output: `null`, but actual output is `null | number`
}

To address this issue, one workaround involves using multiple conditions in the if statement, which is not ideal:

function isRealNumber(input: number | undefined | null): boolean {
    return input !== undefined && input !== null && Number.isFinite(input);
}

const myNumber: number | null = NaN as any;
if (isRealNumber(myNumber) && myNumber !== null && myNumber !== undefined) {
    const b = myNumber;  // correct output: b is number
} else {
    const b = myNumber;  // correct output: `null | number`
}

Is there a way in TypeScript to have a single function that can accurately narrow down the type without causing incorrect results when negative?

Answer №1

Are you in search of a "one-sided" or "fine-grained" type predicate as mentioned in the issue microsoft/TypeScript#15048? Unfortunately, TypeScript does not directly support this at the moment and it is uncertain if any developments will occur.

However, there is a workaround suggested in the same issue. By changing your guarded type from input is number to input is RealNumber, where RealNumber is a type narrower than number, things may start working as expected.

Although there is no actual distinction between RealNumber and number in the type system, you can create a pseudo distinction using a technique known as branded primitives. This involves intersecting a primitive type like number with an object type containing a phantom "brand" or "tag" property. For instance:

function isRealNumber(input: number | undefined | null): input is number & { __realNumber?: true } {
    return input !== undefined && input !== null && Number.isFinite(input);
}

const myNumber: number | null = NaN as any;
if (isRealNumber(myNumber)) {
    const b = myNumber;  // b is number & {__realNumber?: true}
} else {
    const b = myNumber;  // b is number | null
}

This approach seems more rational. When isRealNumber(myNumber) evaluates to true, variable b is narrowed down to

number & {__realNumber?: true}
. Despite the fact that the {__realNumber?: true} property doesn't exist during runtime, it doesn't affect the value of b, which remains a number. On the other hand, when isRealNumber(myNumber) is false, b isn't narrowed at all.

Here's a link to a code snippet on the Playground.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The error message "Can't resolve all parameters for CustomerService" is preventing Angular from injecting HttpClient

I have a customerService where I am attempting to inject httpClient. The error occurs on the line where I commented //error happens on this line. Everything works fine until I try to inject httpClient into my service. The error message is: `compiler.js: ...

Can you explain how the "reduce" function can be implemented using an interface in TypeScript?

Can you explain the "reduce" function using an interface in TypeScript? https://i.stack.imgur.com/X1VxL.png ...

What are the distinctions between generic and discriminated types?

Hi there, I've been thinking of an idea but I'm not sure how to implement it or if it's even possible. Is there a way to create a type SomeType where the first property can be any value from the set T, but the second property cannot be the ...

Creating a function in Typescript that transforms an array into a typed object

Recently, I have started learning TypeScript and I am working on a function to convert arrays from a web request response into objects. I have successfully written the function along with a passing unit test: import { parseDataToObject } from './Parse ...

Unpacking Constructor with visible arguments

In my constructor, I utilize destructuring to simplify the parameters needed to create an object with default values. export class PageConfig { constructor({ isSliding = false }: { isSliding?: boolean; getList: (pagingInfo: PagingInfo) =&g ...

What could be causing the malfunction of getter/setter in a Vue TypeScript class component?

Recently delving into the world of vue.js, I find myself puzzled by the unexpected behavior of the code snippet below: <template> <page-layout> <h1>Hello, Invoicer here</h1> <form class="invoicer-form"> ...

reinstate dummy of external class method in ts-jest

Problem I am encountering an issue while trying to mock a function that is imported from another class and called within the main class. Although I can successfully mock the function and return the specified values, I am unable to revert the mocked functi ...

Using Typescript for Asynchronous Https Requests

I've been attempting all day to make an https request work. My current code isn't functioning as expected; when I run it, I encounter an "Unhandled error RangeError: Maximum call stack size exceeded at Function.entries" import * as https from &q ...

Javascript operations for duplicating and altering arrays

In my Angular application, I am working with an array called subAgencies that is connected to a datasource. I need to implement 2-way binding on this array. Currently, I have a method in place where I copy the contents of the original array to a new one, ...

Utilizing React-hook-Form to transfer data between two SelectBoxes

This simple logic is causing me some trouble. Despite using react-hook-form, I thought this would be easy. However, after struggling with it for over a week, I'm still facing challenges. I'm incorporating nextUI components into my project. < ...

Adding a Unique Variant to Material-UI Component in React/ Typescript

I am currently working on creating a customized component inspired by Material-ui components but with unique styles added. For instance, the Tab component offers variants such as ["standard","scrollable","fullWidth"], and I want to include 'outline ...

Managing Data Types in a React and Express Application

I am working on a project that includes both a React client and a Node-Express backend. Currently, my React app is running with TypeScript and I am looking to switch my backend to TypeScript as well. At the moment, my project structure consists of a clien ...

Using vue-router within a pinia store: a step-by-step guide

I'm currently setting up an authentication store using Firebase, and I want to direct users to the login/logged page based on their authentication status. My goal is similar to this implementation: https://github.com/dannyconnell/vue-composition-api- ...

Looking to adjust the height of a foreignObject element within an SVG?

Looking to dynamically change the height of a foreignObject within an SVG, while also needing HTML elements inside it (working with ngx-graph). <foreignObject x="1" y="1" width="335" [height]="foreignObjHeight(node.Dat ...

Encountering TS2304 error message while running TypeScript files indicates the inability to locate the name 'PropertyKey', in addition to another error - TS2339

I encountered an issue while attempting to execute a spec.ts file in the Jasmine Framework. After installing @types/core-js version 0.9.46 using the command npm i @types/core-js, I started receiving the following error messages: > ../../node_modules/ ...

Module not found when attempting to import a newly created TypeScript module

A fresh Typescript project called puppeteer-jquery has just been released on the NPM registry. The code is functioning perfectly well. However, when attempting to integrate it into another project: npm install puppeteer-jquery and trying to import it lik ...

Converting objects to arrays in Typescript: A step-by-step guide

Can anyone assist me in converting a string to a Typescript array? Any help would be greatly appreciated. Take a look at the following code snippet: private validateEmptyOption(): any { console.log("CHECKED") let isValid = true; this.currentF ...

Tips for constructing node.js projects using local versions of the dependencies?

Recently, I've been tackling a rather intricate node.js project (find it at https://github.com/edrlab/thorium-reader/) while trying to incorporate local versions of certain dependencies. Surprisingly, I can successfully build and execute the project ...

Managing errors with the RxJS retry operator

I'm facing an issue with my RxJS code where I need to continuously retry a data request upon failure while also handling the error. Currently, I am using the retry operator for this purpose. However, when attempting to subscribe to the retry operator ...

Tips for retrieving the present value of a piped/converted BehaviorSubject

How do I retrieve the current value of the observable generated by readValue() below without subscribing to it? var subject = new BehaviorSubject<Object>({}); observe(): Observable<Object> { return subject.pipe(map(mappingfunction)); } Do ...