Ensuring type safety for a generic union type

A new union type has been defined:

type CustomParameterType = number | string | boolean | Array<number>;

An object is created to hold key-value pairs of this union type:

class CustomParameter
{
    constructor(name: string, value: CustomParameterType)
    {
        this.Name = name;
        this.Value = value;
    }

    public Name: string;
    public Value: CustomParameterType;
}

An array of these CustomParameters can be used to store keys and values:

let customParams: Array<CustomParameter> = new Array<CustomParameter>();
customParams.push(new CustomParameter("one", 1));
customParams.push(new CustomParameter("two", "param2"));

By implementing a GetParameter function, typed values can be retrieved from the array. Here's an example:

// should return a number with value 1
let numParam: number | undefined = this.GetParameter<number>("one", customParams);

// should return a string with value "param2"
let strParam: string | undefined = this.GetParameter<string>("two", customParams);

// should return undefined since 'two' is not of type number
let undefParam: number | undefined = this.GetParameter<number>("two", customParams);

However, there seems to be an issue with checking the generic type in the GetParameter function. It might require a type guarding function. Is it possible to resolve this?

Explore the example further in the TypeScript Playground: Playground

Answer №1

When TypeScript is compiled into JavaScript, the type T and its specific details like <number> or <string> are removed, resulting in no reference to T at runtime. This lack of presence means that using the instanceof operator on primitive types such as string and

boolean</code won't yield the desired results (<code>"foo" instanceof String
returns false).

To accommodate for this dynamic, you can introduce a type guard function as an argument in the GetParameter() function, ensuring its existence during runtime.

An updated version of GetParameter() would look like:

function GetParameter<T extends RequestParameterType>(
  parameterName: string,
  parameters: Array<RequestParameter>,
  guard: (x: RequestParameterType) => x is T // new param
): T | undefined {
  let result: T | undefined = undefined;

  for (let parameter of parameters) {
    if (parameter.Name === parameterName && guard(parameter.Value)) {
      result = parameter.Value;
    }
  }

  return result;
}

The guard() function must be able to narrow down an object of type RequestParameterType to type T. Here's an example set of guard functions you can implement:

const guards = {
  number: (x: RequestParameterType): x is number => typeof x === "number",
  string: (x: RequestParameterType): x is string => typeof x === "string",
  boolean: (x: RequestParameterType): x is boolean => typeof x === "boolean",

  numberArray: (x: RequestParameterType): x is number[] => Array.isArray(x) 
};

You can use the GetParameter() function with these guard functions as follows:

let numberParam = GetParameter("one", parameters, guards.number);
console.log(numberParam); // 1

let stringParam = GetParameter("two", parameters, guards.string);
console.log(stringParam); // param2

let undefinedParam = GetParameter("two", parameters, guards.number);
console.log(undefinedParam); // undefined

By replacing <number> with guards.number, you ensure correct type narrowing and expected output values. Good luck with your implementation!

Code Link

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 is: "Cannot access property 'up' of an undefined object within the material UI library using theme.breakpoints."

I am encountering difficulties with the export of makeStyles. Below you can find my code and configuration: import SearchField from "../SearchField"; import { TextField, Select, useMediaQuery, Grid, Button, Box, Fade } from '@material-ui/core&ap ...

The TypeScript error is causing issues in the Express router file

Here is the structure of my module: import * as express from 'express'; let router = express.Router(); router.post('/foo', function(req,res,next){ // ... }); export = router; However, I'm encountering the following error: ...

Learn how to retrieve images from the web API at 'https://jsonplaceholder.typicode.com/photos' and showcase them on a webpage using Angular10

Using the API "https://jsonplaceholder.typicode.com/photos", I have access to 5 properties: albumId: 1 id: 1 thumbnailUrl: "https://via.placeholder.com/150/92c952" title: "accusamus beatae ad facilis cum similique qui sunt" url: "https://via.placeh ...

The method this.$el.querySelector does not exist

The data retrieved from the database is inserted into the input fields and submitted as a form. This data is an object that passes the value to the database. However, when I trigger this form, an error occurs. See example of the error <input id=" ...

Mastering the art of Typescript typing

I am attempting to start the REST server for an Aries agent as outlined here: import { startServer } from '@aries-framework/rest' import { Agent } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/nod ...

EventListener cannot be removed

My TypeScript class is structured like this: class MyClass { let canvas: any; constructor(canvas: any) { this.canvas = canvas; this.canvas.requestPointerLock = this.canvas.requestPointerLock; document.exitPointerLock = ...

Is there a way to verify the presence of multiple array indices in React with TypeScript?

const checkInstruction = (index) => { if(inputData.info[index].instruction){ return ( <Text ref={instructionContainerRef} dangerouslySetInnerHTML={{ __html: replaceTextLinks(inputData.info[index].instruction) ...

Having trouble getting ng-click to function properly in TypeScript

I've been struggling to execute a function within a click function on my HTML page. I have added all the TypeScript definition files from NuGet, but something seems to be going wrong as my Click Function is not functioning properly. Strangely, there a ...

Place information from an input field into a specific row within a table

Utilizing Angular 4, I am developing a frontend application for a specific project. The interface features a table with three rows that need to be filled with data from an external source. https://i.stack.imgur.com/Dg576.png Upon clicking the "aggiungi p ...

Discover the subsite inventory of a SharePoint site using TypeScript

Is there a way to gather all sub-sites from my SharePoint site and organize them into a list? I initially thought of using this.context.pageContext, but could not locate it. Please excuse my seemingly simple question, as I am still learning TypeScript. ...

Incorporate an external JS file (File A) that is dependent on another JS file (File B) into a TypeScript file within the context of Angular 4

Working on an Angular 4 project, I recently installed two external JS libraries using npm. They are now in the node_modules folder and usable in another TS file within my project. The issue arises because import B requires import A, preventing me from effe ...

Exclude the initial argument from functions listed within a JSON structure

Is there a way to create a generic type that reflects a JSON object structure, but excludes the first argument from functions while maintaining the types of other arguments? type InputType<T> = { increment: (state: T) => T, add: (state: T, cou ...

Angular keeps FormArray elements' validity up-to-date as new elements are added to the array

I am facing an issue where I do not want the validators to run unnecessarily. Every element of FormArray is being validated asynchronously, so I prefer the validators to be triggered only when the control's value actually changes. It seems odd that va ...

Capture Video on iOS devices using the MediaRecorder API and display it using HTML5 Video Player

Issue: I am facing an issue where I cannot record video or get a video stream from the camera on iOS through my Angular web application built using ng build. Investigation: In my investigation, I explored various websites that discuss Apple iOS features ...

Having trouble retrieving information from Node.js service in AngularJS 2

I am currently expanding my knowledge of Angular and attempting to retrieve data from a node js service using Angular 2 services. When I access the node js services directly from the browser, I can see the results. However, when I attempt to fetch the dat ...

Applying a Typescript Generic to enhance the functionality of the API fetcher

I've written a simple function to enhance fetch functionality. I am currently exploring how TypeScript Generics can be utilized to define the Type for 'data' in the return. const apiFetchData = async ( url: string, options = {}, ): P ...

When clicking initially, the default input value in an Angular 2 form does not get set

I am currently learning angular2 as a beginner programmer. My goal is to create a form where, upon clicking on an employee, an editable form will appear with the employee's current data. However, I have encountered an issue where clicking on a user f ...

Storing numerous string labels and arrays in a TypeScript associative array

I am currently developing a mobile app using Ionic 4 where I need to store various labels and arrays in an associative array. However, I am encountering challenges when it comes to initializing the array, adding new items to it, and updating existing ones ...

What is the method for retrieving the name of an object's property within an Angular template

I am trying to display the name of a nested object's property using Angular interpolation <ng-container ngFor="let item of saleDetailsAggegater.productMap | keyvalue"> <tr *ngFor="let qtyMap of item.value | keyvalue"&g ...

ESLint not functioning properly on TypeScript (.ts and .tsx) files within Visual Studio Code

After installing the ESLint extension in VSC, I encountered an issue where it was no longer working on the fly for my React project when I introduced Typescript. In the root of my project, I have a .eslintrc file with the following configuration: { "pa ...