What is the syntax for specifying that a type must belong to a union in Typescript?

I have a scenario where I want to achieve something similar to the following:

type Whoozit = string | number

type WhoozitMap = {[key: string]: (x: Whoozit) => boolean}

const whoozit: WhoozitMap = {
    cheese: (x: string) => x === 'cheese',
    digit:  (x: number) => x < 10
}

However, the above code doesn't work as intended. The type of the functions in the map must be Whoozit, not just string or number. So, there are two ways to fix this issue:

const whoozit: WhoozitMap = {
    cheese: (x: Whoozit) => { if (isString(x)) { return x === 'cheese' } else { return false } },
    digit: (x: number)  => { if (isNumber(x)) { return x < 10 } else { return false } },
}

// Although this is a solution, it adds unnecessary complexity as 
// my code runtime can handle selecting the correct key from the map

or

type WhoozitMap = {[key: string]: (x: any) => boolean}

// This approach is cleaner, but it sacrifices all type safety within the map

Is there a way to express what I want in Typescript? Specifically, the following code snippet should compile:

const whoozit: WhoozitMap = {
    cheese: (x: string) => x === 'cheese',
    digit:  (x: number) => x < 10
}

... whereas this example should not be valid?

const whoozit: WhoozitMap = {
    cheese: (x: string) => x === 'cheese',
    list:   (x: number[]) => x.includes(22) // ❌ number[] does not extend Whoozit
}

Check out the Typescript Playground.

Answer №1

To ensure the WhoozitMap type is able to handle functions that require a Whoozit type as an argument, you can define it in such a way that only functions meeting this criteria are correctly assigned.

type Whoozit = string | number;

type WhoozitMap = {
    [Key in 'string' | 'number']: (x: Whoozit) => boolean;
};

const whoozit: WhoozitMap = {
    string: (x: Whoozit) => typeof x === 'string' && x === 'cheese',
    number: (x: Whoozit) => typeof x === 'number' && x < 22
};

Answer №2

If you prefer to organize the type definition outside of the function, one approach is to declare it separately:

type WhoozitFunction = ((x: number) => boolean) | ((x: string) => boolean)
type WhoozitMap = {[key: string]: WhoozitFunction}

However, if your intention is to reuse the Whoozit type, you can utilize distributive conditional types:

type ToPredicate<Type> = Type extends any ? (x: Type) => boolean : never;
type WhoozitMap = {[key: string]: ToPredicate<Whoozit>}

This solution achieves the same outcome as the previous one.

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

Replace i18next property type in React for language setting

We have decided to implement multilanguage support in our app and encountered an issue with function execution. const someFunction = (lang: string, url: string) => any If we mistakenly execute the function like this: someFunction('/some/url', ...

"Exploring the concepts of inheritance in Typescript

I'm seeking answers regarding inheritance in TypeScript/JavaScript. Below is my base class (express controller router): abstract class BaseCtrl { abstract model; // Get all getAll = (req, res) => { this.model.find({}, (err, docs) => ...

New Requirement for Angular Service: Subclass Constructor Must Be Provided or Unable to Resolve all Parameters for ClassName (?)

During a recent project, I encountered an issue while working on several services that all extend a base Service class. The base class requires a constructor parameter of HttpClient. When setting up the subclass with autocomplete, I noticed that their con ...

Exciting Update: Next.js V13 revalidate not triggering post router.push

Currently using Next.js version 13 for app routing, I've encountered an issue with the revalidate feature not triggering after a router.push call. Within my project, users have the ability to create blog posts on the /blog/create page. Once a post is ...

Intellisense not working with express

After using the command npm install --save @types/express to install, I imported in my ts file as follows: import * as express from "express"; var app = express(); Despite this setup, I am not able to get intelisense on the app variable. Additionally ...

Iterate over an array of objects containing identical data on certain objects and display it only once

I am working with an array of product objects that look like this products: [ { id: 1, drinkName: "Chivita", category: "Juice", description: "The best drink ever" }, { id: 1, drinkName: " ...

Namespace remains ambiguous following compilation

I'm currently developing a game engine in TypeScript, but I encountered an issue when compiling it to JavaScript. Surprisingly, the compilation process itself did not throw any errors. The problem arises in my main entry file (main.ts) with these ini ...

Can someone guide me on incorporating static methods into a Mongoose model using TypeScript for Mongoose version 5.11?

When using Mongoose 5.10, I implemented static methods in my Mongoose models with the following pattern: import { Schema, Document, Model, Connection } from "mongoose"; import { v4 } from "uuid"; export interface IFoo extends Document ...

merging JavaScript objects with complex conditions

I am attempting to combine data from two http requests into a single object based on specific conditions. Take a look at the following objects: vehicles: [ { vId: 1, color: 'green', passengers: [ { name: 'Joe', ag ...

Typescript issue when a value is possibly a function or null

I have defined a type called StateProps with the following properties type StateProps = { isPending: boolean, asyncFn: (...args: any[]) => void | null } To initialize, I set up an initialState variable where the asyncFn property is initially s ...

Tips for implementing dynamic properties in TypeScript modeling

Is there a way to avoid using ts-ignore when using object properties referenced as strings in TypeScript? For example, if I have something like: const myList = ['aaa', 'bbb', 'ccc']; const appContext = {}; for (let i=0; i< ...

Angular's DecimalPipe will truncate any strings that exceed 10 digits

Using the decimal pipe to format numbers in an input field value| number:'0.0-6': 'en-us' When working with numbers containing more than 10 digits, it displays as follows: For 11111111111.123456, it formats to 11,111,111,111.123455 ...

Showcase pictures within an angular smart table

Is it possible to display images in a column within an ng smart table? We have several columns consisting mostly of data, with one column dedicated to displaying images. Following the ng smart table concept, I attempted to implement the code below which cu ...

What is the best way to reduce the size of TypeScript source code in an Electron application with the help of Electron Forge and Electron Packager

resolved: I was able to solve this issue using electron-builder, which utilizes webpack in the background to handle all problems efficiently. Initially, I faced this challenge while using electron-forge and electron-packager. Despite researching extensivel ...

Importing Modules in Angular: Explicit vs Implicit Approach

My current setup includes a SharedModule that is imported by every other module. This means that some modules have an implicit import to the SharedModule because they import other modules that already import it. I'm curious if this could potentially i ...

Tips for implementing accurate structure on CloudFrontWebDistribution class

I seem to be facing an issue while trying to create a new instance of the CloudFrontWebDistribution using aws-cdk v1.7. The compiler is showing some dissatisfaction with the construct I provided. import { Stack, StackProps, Construct, App } from '@aw ...

Rotating images on a canvas

We're currently implementing Ionic and Angular in our project. One issue we are facing is regarding image rotation on canvas. When we click on an image, the rotation works perfectly if it's a jpg file. However, when we pass a base64 image, the r ...

Error in TypeScript: Objects can only specify properties that are known, and 'state' is not found in type 'Partial<Path>'

As I strive to pass props through a React Router Link, my goal is to include all the user props. The code below is causing an error, particularly where state: {...employee} is highlighted. Although I am relatively new to TypeScript, I am actively working ...

Unexpected expression after upgrading to TypeScript 3.7.2 was encountered, file expected.ts(1109)

After updating TypeScript from version 3.6.x to 3.7.2, I started using optional chaining in my code. However, I encountered a peculiar error. Error message: Expression expected.ts(1109) This error appeared in both my (vim, VSCode) IDE, even though the ...

Searching for a specific value in a string using Javascript and then displaying

I am working on a react typescript project where I have an array called result that contains a URL with the format /Items(142)/. My goal is to extract the item ID from this URL and store it in a variable. I am aware of using something like result.data.oda ...