Ensuring the validity of various data types using typeguard

How can I create a type guard that validates multiple types? Here is what I have in mind:

interface A { }

interface B extends A {
  foo: number;
}

interface C extends A {
  bar: number;
}

function checkType<T extends A>(item: A): item is T {
  if((item as B).foo)
    // validate as B
  if((item as A).bar)
    // validate as A
}

This is how I plan to implement it:

if(checkType<B>(item)) {
// do something 
}

Answer №1

It has been mentioned that achieving what you desire is not feasible.

During the compilation of TypeScript code, the static type system gets erased. Therefore, consider the following TypeScript code snippet:

const item: A = { foo: 123 };
if (is<B>(item)) { console.log(item.foo.toFixed(2)); }
if (is<C>(item)) { console.log(item.bar.toFixed(2)); }

The resulting JavaScript code after compilation could look like this:

const item: A = Math.random() < 0.5 ? { foo: 123 } : { bar: 456 };
if (is(item)) { console.log(item.foo.toFixed(2)); }
if (is(item)) { console.log(item.bar.toFixed(2)); }

Hence, at runtime, it becomes essential for `is(item)` to function as a `B` check in one instance and a `C` check in the next. This scenario requires uniqueness in results based on type information which isn't inherently present. It delves into the realm of supernatural capabilities beyond standard human-made software functionality.


To implement something achievable without resorting to magic, the solution lies in providing your function with runtime data to determine the appropriate checks. For example, tweaking `is()` to include a second parameter containing the type name to be checked:

const item: A = Math.random() < 0.5 ? { foo: 123 } : { bar: 456 };
if (is(item, "B")) { console.log(item.foo.toFixed(2)); }
if (is(item, "C")) { console.log(item.bar.toFixed(2)); }

This updated approach enables the `is()` implementation to utilize the second parameter to determine the property to validate at runtime. While the solution may seem convoluted, it's viable and comes with an appropriately defined type signature showcasing its integrity:

type PossibleTypes = B | C;
const props = {
    B: "foo",
    C: "bar"
} as const
type Props = typeof props;

function is<K extends keyof Props>(
    item: A,
    typeName: K
): item is Extract<PossibleTypes, Record<Props[K], number>> {
    const key = props[typeName];
    return ((key in item) && (typeof (item as any)[key] === "number"));
}

In this new setup, a precise union type `PossibleTypes` enumerates the types eligible for checking within `is()`. The `props` object maps type names "B" and "C" to their respective properties for verification. Also, `Props` represents the type of `props` in use.

The `is()` function employs generics via `K` for the type name, whereas the result constitutes a type predicate confirming if `item` aligns with either type `B` or `C`. Type extraction from the `PossibleTypes` union ensures accuracy by verifying a numeric property matching the key specified in `Props[K]`.

By mapping type names to valid keys and validating these keys within `item`, the `is()` mechanism adeptly performs the necessary property verifications.


While there exist alternate paths to transform `is()` into a manageable form, each alteration might yield similar levels of dissatisfaction due to minimal gains from consolidating diverse checks within a single type guard function. With only two types, namely `B` and `C`, opting for simplicity by creating distinct check functions might offer a clearer path forward:

function isB(item: A): item is B {
    return ("foo" in item) && (typeof (item as any).foo === "number");
}
function isC(item: A): item is C {
    return ("bar" in item) && (typeof (item as any).bar === "number");
}
const item: A = Math.random() < 0.5 ? { foo: 123 } : { bar: 456 };
if (isB(item)) { console.log(item.foo.toFixed(2)); }
if (isC(item)) { console.log(item.bar.toFixed(2)); }

Your decision ultimately hinges on specific usage scenarios and personal preferences.


Click here for Playground link to the code

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

How can I adjust the timeout or enhance my code for Excel Online's ExcelScript error regarding the Range getColumn function timing out?

I am struggling with a code that is supposed to scan through the "hello" sheet and remove any columns where the top cell contains the letter B: function main(workbook: ExcelScript.Workbook) { let ws = workbook.getWorksheet("hello"); let usedrange = ws ...

Adding items to a Mat Menu dynamically during execution

I am utilizing the mat menu to showcase the available options in my project as a breadcrumb. However, when I attempt to add a new item, it successfully adds to the database and reflects in the array object, but unfortunately, the Angular mat-menu does not ...

Tips on effectively rendering child components conditionally in React

My components currently consist of an AddBookPanel containing the AddBookForm. I am looking to implement a feature where the form is displayed upon clicking the 'AddBookButton', and hidden when the 'x' button (image within AddBookForm c ...

Retrieve the name from the accordion that was clicked

Hey there, I have a simple accordion that is based on an API called "names". <div *ngFor="let item of showDirNames | async | filter: name; let i = index;"> <button class="accordion" (click)="toggleAccordian($event, i)&q ...

Using React.js and TypeScript to leverage a single component for both <input /> and <textarea /> elements

After developing an <Input /> component with helper components for improved usability and code reduction within the main component, I encountered an issue while attempting to create a <textarea /> HTML element. The problem arises from using Rea ...

Exploring the differences between Typescript decorators and class inheritance

I find myself puzzled by the concept of typescript decorators and their purpose. It is said that they 'decorate' a class by attaching metadata to it. However, I am struggling to understand how this metadata is linked to an instance of the class. ...

What is the method for throwing errors with NestJS guards without relying on an authentication module?

Are you looking to customize error responses in NestJS guards? import { CanActivate, Injectable, ExecutionContext, NotFoundException } from '@nestjs/common'; import { Observable } from 'rxjs'; import { InjectModel } from '@nestjs/m ...

Rendering illuminated component with continuous asynchronous updates

My task involves displaying a list of items using lit components. Each item in the list consists of a known name and an asynchronously fetched value. Situation Overview: A generic component named simple-list is required to render any pairs of name and va ...

The Azure function application's automatic reload feature does not function properly with the v4 model

Struggling to get Azure Function to recognize and incorporate changes in source code. I have set up a launch task to initiate the local server and run an npm script with tsc -w in watch mode. While I can see the modifications reflected in the /dist folder ...

The challenge of resizing dialog elements in Angular Material

I am currently working on developing a text dialog for my app that will be reused frequently. The text dialog consists of a header, a message displayed above the text input, the text input area, and "Ok" and "Cancel" buttons. Upon my observation, I have f ...

What is the method for inserting a loop into a print template?

There is a print method available: printData(data): void { console.log(data); let printContents, popupWin; popupWin = window.open(); popupWin.document.write(` <html> <head> <title>Print tab</title> ...

VS-Code is not a fan of accessors when targeting ES6

I can't seem to figure out this strange issue I'm having with VS-Code (1.13.1, MacOS). Every time I try to use a class getter or setter, I get the following error: [ts] Accessors are only available when targeting ECMAScript 5 and higher. The ...

TypeScript fails to recognize when variable has already undergone undefined or null value check

Consider the following scenario: TypeScript fails to recognize that parameters.a and parameters.b have been checked for undefined values, leading to a potential issue where transformValue(parameters.a) line might return an undefined value: type Scenario = ...

Guide on incorporating a bootstrap button into a React component using typescript

I am looking to create a custom React component using Typescript that wraps around a Bootstrap button. This component should allow me to pass parameters such as width, height, color, etc. These parameters would then be used to modify the appearance of the ...

Angular Routing can be a powerful tool for managing multiple article posts in an efficient and organized way

I am in the process of building a website with Angular that features numerous articles. Whenever a user clicks on an article, I want it to navigate to a new URL using routing. To achieve this, I have created a new Article component and here is how my app- ...

How can you obtain the key that comes after or before an existing key in a sorted TypeScript string enum?

Given the key STEPS.GENDER and the string enum below export enum STEPS { NAME = "name", GENDER = "gender", BIRTHDAY = "birthday", SUCCESS = "success" } Is there a way to programmatically determine the next o ...

Could there be a more effective approach to managing interfaces and eliminating 'TypeErrors'?

I'm encountering a TypeError issue in my ReactJS project. The error message states: Server Error: TypeError: Cannot read properties of undefined (reading 'phoneNumber') ------------------------------------------------------------------------ ...

index signature in TypeScript is an optional feature

Is it possible to create a type with optional namespaces in TypeScript? export interface NodesState { attr1: number; attr2: number; attr3: number; } The goal is to allow users to namespace the type like this: { namespace1: { attr1: 100, ...

Facing issue with local redis session not functioning as intended

I'm encountering an issue with my redis session not functioning properly when testing locally. EDIT: Additionally, I realized that it's failing to save a cookie when trying to set req.session[somekey] as undefined like so: req.session.user = u ...

Leverage environment variables within your index.html file

Currently, I am using Angular and I am encountering an issue while attempting to utilize an environment variable in my index.html for Google Analytics. I have attempted the following approach: <script> import {environment} from "./environments/e ...