Identify whether the type is a 'string' constant, 'number' constant, or 'string | number' constant

With the recent addition of conditional types in typescript, I have delved into meta-programming to enhance VSCODE intellisense. However, I am facing challenges when it comes to identifying literal types compared to other types that can be easily distinguished using A extends B.

Therefore, my inquiry is - what methods can I use to ascertain if a given type is a literal type?

Answer №1

When considering different scenarios, I personally suggest the following approach:

type IfStringOrNumberLiteral<T, Y=true, N=false> =
  string extends T ? N :
  number extends T ? N :
  [T] extends [never] ? N :
  [T] extends [string | number] ? Y :
  N

I prefer using --strictNullChecks in my code, but depending on your requirements, handling of null and undefined may vary. Feel free to adjust this as needed for your specific use case. This provides an alternative to convoluted constructs like

( X extends Y ? true : false ) extends true ? U : V
.

Hopefully this suggestion proves helpful; best of luck.

Answer №2

Update: I've reformulated everything to adhere to jcalz's clean style:

type IsStringLiteral<T> =
    string extends T ? false : // must be narrower than string
    [T] extends [never | undefined | null] ? false : // must be wider than never and nullable
    [T] extends [string] ? true : // must be wider than string
    false;

type IsNumberLiteral<T> =
    number extends T ? false : // must be narrower than number
    [T] extends [never | undefined | null] ? false : // must be wider than never and nullable
    [T] extends [number] ? true : // must be wider than number
    false;

type IsSingleTypeLiteral<T> =
    IsStringLiteral<T> extends false ?
    IsNumberLiteral<T> :
    true;

type IsLiteral<T> =
    string extends T ? false : // must be narrower than string
    number extends T ? false : // must be narrower than number
    [T] extends [never | undefined | null] ? false : // must be wider than never and nullable
    [T] extends [number | string] ? true : // must be wider than number | string
    false;

That was a bit more challenging than expected, but after hours of work I finally accomplished this:

type Switch<A, B, IF, ELSE = A> = A extends B ? IF : ELSE;
type IsStringLiteral<T> =
    // Check for nullable type using Switch type. See next comment why Switch must be used.
    Switch<T, undefined | null, true, false> extends true ? false : (
        // `T extends string` does not work for `"str" | number` and etc. Results in `boolean` type.
        // Need to use boolean Switch to filter out false-positive.
        Switch<T, string, true, false> extends true ? (
            // `string` does not extend literal type.
            string extends T ? false : true
        ) : false
    );
type IsNumberLiteral<T> =
    Switch<T, undefined | null, true, false> extends true ? false : (
        Switch<T, number, true, false> extends true ? (
            number extends T ? false : true
        ) : false
    );
type IsSingleTypeLiteral<T> =
    Switch<IsStringLiteral<T>, false, IsNumberLiteral<T>, true>;
type IsLiteral<T> =
    // `"string literal" | string` and etc. will return a false-positive `boolean` type.
    // `boolean` type must always be `false`, thus `false extends boolean` is used to get that `false` type.
    Switch<false, Switch<T, undefined | null, true, false> extends true ? false : (
        T extends string | number ? (
            string extends T ? false : (number extends T ? false : true)
        ) : false
    ), false, true>;

Here are some demonstration cases presented in the form of an HTML table (tested with 3.1.1):

table, th, td {
  white-space: nowrap;
  border: 1px solid black;
}
<table><tbody><tr><th>Test scenarios</th><th>IsStringLiteral</th><th>IsNumberLiteral</th><th>IsSingleTypeLiteral</th><th>IsLiteral</th></tr><tr><td>"string literal"</td><td><b>true</b></td><td>false</td><td><b>true</b></td><td><b>true</b></td></tr><tr><td>123</td><td>false</td><td><b>true</b></td><td><b>true</b></td><td><b>true</b></td></tr><tr><td>string</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>object</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>[]</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>[string, number]</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>any</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>void</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>null</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>undefined</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>never</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>"string literal" | 123</td><td>false</td><td>false</td><td>false</td><td><b>true</b></td></tr><tr><td>"string literal" | string</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>123 | number</td><td>false</td><td>false</td><td>false</td><td>false</td></tr></tbody></table>

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

I create an `emitHandler` in the composition API and ensure it is properly typecasted within my com

I have created an emit within my <script setup lang="ts"> and I want to pass that emit to my composable so it can handle the emitting process. However, I need to ensure that the received parameter is properly casted (as using any is not id ...

Strategies for resolving a mix of different data types within a single parameter

Here, I am setting up the options params to accept a value that can either be a single string or another object like options?: string[] | IServiceDetail[] | IServiceAccordion[]; However, when attempting to map these objects, an error is encountered: Prope ...

Retrieving data from a nested JSON array using AngularJS

I am struggling to navigate the intricate nested tree view of child items within a JSON array. I have been grappling with this challenge for days, trying to figure out how to access multiple children from the complex JSON structure. Can someone provide g ...

Retrieve the imported object by referencing the environment

Within my TypeScript Node application, I am looking to access the exported object that corresponds to my NODE_ENV variable. config.ts const test: { [index: string]: any } = { param1: "x", param2: { name: "John" } } const dev: { [index ...

Having trouble launching my angular application in the development environment

My Angular application builds and runs successfully in production mode, but I'm facing issues running it in development mode. When I try the command: npm exec ng serve -c dev --port 4200 --proxy-config proxy.conf.json and add -c dev, I encounter an er ...

Developing a bespoke React Typescript button with a custom design and implementing an onClick event function

Currently, I am in the process of developing a custom button component for a React Typescript project utilizing React Hooks and Styled components. // Button.tsx import React, { MouseEvent } from "react"; import styled from "styled-components"; export int ...

Error: The function jquery_1.default is not recognized by webpack

I am encountering an issue with using external imports of jQuery in webpack. Despite trying different import syntaxes such as import $ from 'jquery', import * as $ from 'jquery, and const $ = require('jquery'), I continue to receiv ...

The function signature '(event: ChangeEvent<HTMLInputElement>) => void' does not match the expected type 'ChangeEvent<HTMLInputElement>'

This is my first time using TypeScript to work on a project from the ZTM course, which was initially written in JavaScript. I am facing an issue where I am unable to set a type for the event parameter. The error message I receive states: Type '(event: ...

Instructions on transferring information from the app.component to its child components

I am currently working with Angular 6 and I have a specific requirement. I need to send data retrieved from an external API in my app.component to other child components. Instead of repeatedly calling the common API/service in every component, I want to ma ...

A method for replacing keys within an object with those from another object

I am looking for a way to remove keys from objects within another object using TypeScript. let before = { '0': { 'title':'title 1', 'time':'12.30pm', }, '1': { 'title&apos ...

There is no overload that fits this call. The string type cannot be assigned to the Signals type

Lately, I've been utilizing Typescript for constructing a microservice and managing signals. The code was running smoothly until a few days back, but now it's throwing errors without any apparent fix. This is an excerpt of the code responsible f ...

Troubles encountered while attempting to properly mock a module in Jest

I've been experimenting with mocking a module, specifically S3 from aws-sdk. The approach that seemed to work for me was as follows: jest.mock('aws-sdk', () => { return { S3: () => ({ putObject: jest.fn() }) }; }); ...

Oops! An unexpected error occurred while parsing the JSON response

While working with my Json file, I encountered an error that has been validated on https://jsonlint.com/ @Injectable() export class LightParserService{ ITEMS_URL = "./lights.json"; constructor(private http: Http) { } getItems(): Promise<Light[ ...

Generics in Typescript implemented for a React component that accepts an array of records along with an array of

I am currently working on developing a straightforward typed react component designed to display a table from an array of objects. The input data is structured as follows: // array of records containing data to render in the table data = [ { one: 1, ...

What is the best way to have an icon appear when a child component div is clicked, without it displaying on other similar divs?

Within my child component div, I have configured it to display information from an object located in the parent component. Initially, when the web app loads, it correctly shows three divs with names and messages retrieved from the created object. However, ...

Utilize a service injected through dependency injection within a static constant defined within the component itself

Using Angular, I am trying to utilize a service that has been injected through dependency injection as a value for a static variable. Here is the scenario: Starting with the constructor where the Helper Service is injected: constructor(private _helper ...

Angular chat integration

In my application, I have a parent component called "chat" with two child components - "sidebar" (which displays the user list) and "conversation detail" (which shows the chat with each user). The functionality I am aiming for is that when a user is clicke ...

Angular Universal causing issues with updating the DOM component

@Component({ selector: 'mh-feature-popup', template: ` <div class="full"> <div> <div class="container-fluid" [@featurepop]="state"> <div class="row"> <div class="col-xs-12 col-md-4 col-md-offse ...

Validating mixed types and generics within an array using Typescript's type checking

I am currently working with a setup that involves defining interfaces for animals and their noises, along with classes for specific animals like dogs and cats. I am also storing these animals in an array named pets. interface Animal<T> { name: stri ...

What is the best method for expanding nodes after the tree has been loaded?

Greetings! I am currently working on a web application using Angular 5. One of the features I have implemented is loading trees onto my webpage. These trees are populated with data from an API and are designed to be dynamic. After loading the tree, my goal ...