What is the method to assert that two arguments are equal within a function?

When working with TypeScript, you can pass a value to a function and have that function assert the value is true for type narrowing. For example:

function assertTrue(v: unknown, message: string): asserts v {
  if (!v) {
    throw new MySpecialError(message)
  }
}

But what if you want a higher-level function that constructs a v? For instance:

function assertEqual(v: unknown, expected: unknown) {
  assertTrue(v === expected, `expected ${v} to be ${expected}`)
}

Unfortunately, calling assertEqual does not result in any type narrowing. A workaround is:

function assertEqual<T>(v: unknown, expected: T): asserts v is T {
  assertTrue(v === expected, `expected ${v} to be ${expected}`)
}

However, this would require calls like

assertEqual<'foo'>(v, 'foo')
, which is prone to mistakes.

In the following example, nameOf and nameOf3 both pass type checking, but nameOf2 does not:

export type Staged =
  | {
      stage: 'pre-named';
    }
  | {
      stage: 'named';
      name: string;
    };

class MySpecialError extends Error {}

function assertTrue(v: unknown, message: string): asserts v {
  if (!v) {
    throw new MySpecialError(message)
  }
}

function nameOf(staged: Staged) {
    assertTrue(staged.stage === 'named', 'must be named')
    return staged.name
}

function assertEqual<T>(v: unknown, expected: T): asserts v is T {
  assertTrue(v === expected, `expected ${v} to be ${expected}`)
}

function nameOf2(staged: Staged) {
    assertEqual(staged.stage, 'named')
    return staged.name
}

function nameOf3(staged: Staged) {
    assertEqual<'named'>(staged.stage, 'named')
    return staged.name
}

Answer №1

To extract the specified generic type information from the second parameter, you can do it like this:

as futur mentioned, you have the option to utilize a const assertion to guide the compiler in inferring a string literal type from an argument:

function nameOf2 (staged: Staged): string {
  assertStrictEquals(staged.stage, 'named' as const);
  return staged.name;
}

If you wish to create a specific assertion function solely for deducing string literal values, you can implement it like this:

function assertIsString <T extends string>(actual: string, expected: T): asserts actual is T {
  assert(actual === expected, 'Strings not equal');
}

Answer №2

Consider implementing Template Literal Types:

type ValidateType<T> = `${any & T}`;

function validateEquality<K, T extends (unknown extends T ? ValidateType<K> : K)>(value: unknown, expected: T): asserts value is T {
  assertTrue(value === expected, `expected ${value} to be ${expected}`)
}

function getNamed(staged: Staged) {
    assertTrue(staged.stage === 'named', 'must be named')
    return staged.name
}

function getNamedAlt(staged: Staged) {
    validateEquality(staged.stage, 'named')
    return staged.name
}

function getNamedVersion3(staged: Staged) {
    validateEquality<string, 'named'>(staged.stage, 'named')
    return staged.name
}

View the complete example on TS 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

Encountering an Angular 13 ChunkLoadError during application deployment, despite the presence of the respective chunk

We encountered an issue with our application's upgrade from Angular 11 to 13. While running 'ng serve' on the local machine works fine, deploying it to our Azure app service causes the lazy loaded modules to fail loading. The specific error ...

Retrieve a specific subset of a union based on the class in a generic function

This question shares similarities with another post I made, but this time focusing on using classes instead of plain objects. class Exception1 extends Error { constructor(message: string, public arg1: string) { super(message); } } class Ex ...

The code within a for loop may not function properly when placed within the ngOnInt() function

I am struggling with running a for loop inside ngOnInit in Angular. I have a service that has a method getAllNews() which returns an array, not an observable. Since I can't use the subscribe() method, I want to iterate over this array. When I console ...

Is Typescript familiar with the import -> require syntax, but unfamiliar with the require -> import syntax?

My code includes a simple file that utilizes the ES6 module loader: 1.ts import {foo} from "./2" foo('hello'); 2.ts function foo (a){console.log(a);}; export {foo} When the tsconfig is configured as follows: "module": "commonjs", We can o ...

Another component's Angular event emitter is causing confusion with that of a different component

INTRODUCTION In my project, I have created two components: image-input-single and a test container. The image-input-single component is a "dumb" component that simplifies the process of selecting an image, compressing it, and retrieving its URL. The Type ...

What are the steps to creating an Observable class?

I am working with a class that includes the following properties: export class Model { public id: number; public name: string; } Is there a way to make this class observable, so that changes in its properties can be listened to? I'm hoping fo ...

Using React TypeScript to trigger a function upon route changes with react-router-dom

I am trying to use the useEffect hook to console log every time the location changes in my project. However, when I try to compile, I receive an error in the terminal saying Unexpected use of 'location' no-restricted-globals. I would like to fin ...

Use an observable stream instead of nesting promise.all to aggregate data from an array

In our Angular application, we have a method that combines the results of 3 APIs into a single list. loadPlaces$ = this.actions$.pipe( ofType(PlaceActionTypes.LOAD_PLACES), switchMap((action: LoadPlaces) => from(this.service.findAreas()). ...

Inject Angular 2 component into designated space

I am working on a website that requires a settings dialog to be loaded in a designated area upon clicking a button. The settings dialog is a component that retrieves data from REST endpoints. I am hesitant to simply insert the component and hide it as I ...

Expanding the capability of a function by inheriting properties of either type any or unknown

Can you explain why the values of P1 and P2 are different in these type definitions? type P1 = (() => 22) extends {[k:string]:any} ? 1:2 //`P1 == 1` type P2 = (() => 22) extends {[k:string]:unknown} ? 1:2 //`P2 == 2` ...

Steps to resolve the issue of being unable to destructure property temperatureData from 'undefined' or 'null' in a React application without using a class component

CODE: const { temperatureData } = state; return ( <> <div className="flex flex-row"> {temperatureData.map((item, i) => ( <div className="flex flex-auto rounded justify-center items-center te ...

What is the best way to define a function agreement in Typescript?

I have created a function that can return `undefined` only when its argument is also `undefined`, otherwise it will always return a value derived from the argument provided. Here's an example of how the function works: function triple(value?: number) ...

Create a function in JavaScript that is able to accept a variable number of objects as arguments

I have a good grasp of how to pass infinite parameters in a function in JavaScript. But what about accepting any number of objects as parameters in a function? This is my current implementation: function merge<T>(objA: T, objB: T){ return Object. ...

Encountering [Object] error in Angular's ngbTypeahead functionality

Here is the code for my input field in component.html: <input type="text" class="form-control" [(ngModel)]="searchQuery" [ngbTypeahead]="recommends" name="searchQuery" typeaheadOptionField="user ...

JavaScript: Manipulating Data with Dual Arrays of Objects

//Original Data export const data1 = [ { addKey: '11', address: '12', value: 0 }, { addKey: '11', address: '12', value: 0 }, { addKey: '12', address: '11', value: 0 }, { addKey: &a ...

Send information through a form by utilizing a personalized React hook

I'm having difficulty understanding how to implement a hook for submitting a form using fetch. Currently, this is what I have. The component containing the form: const MyForm = (): ReactElement => { const [status, data] = useSubmitForm('h ...

What is the best way to bring in styles for a custom UI library in Angular 2?

In the realm of UI libraries, I find myself facing a dilemma. Upon importing SCSS styles into my components (such as a button), I noticed that if I instantiate the component button twice in the client app (which has my UI library as a dependency), the SCSS ...

Decorator in React that automatically sets the display name to match the class name

Is there a way to create a decorator that will automatically set the static property displayName of the decorated class to the name of the class? Example usage would be: @NamedComponent class Component extends React.Component { \* ... *\ } ...

The firebase-admin module encounters compatibility issues with middleware due to the OS Module

I'm facing a major issue with my API implementation. I am working on integrating an authentication and verification middleware, but the problem arises when using firebase-admin due to its dependencies on Edge Runtime modules that are incompatible with ...

Using a pipe in multiple modules can lead to either NG6007 (having the same pipe declaration in more than one NgModule) or NG6002 (failing to resolve the pipe to an NgModule

My Pipe is located in apps\administrator\src\app\modules\messenger\pipes\custom-message.pipe.ts import { Pipe, PipeTransform } from '@angular/core'; /** * Pipe for Custom Message for boolean */ @Pipe({ name ...