What is the reason for TypeScript's refusal to accept this task?

In my attempt to create a type that can be A, B, or an object with a key containing an array of 2 or more items that are either A, B, or another similar object (thus allowing for recursive definition).

This is the solution I came up with:

type A = {
    prop1: string;
}

type B = {
    prop2: number;
}

type Array = {
    list: [AorB, AorB, ...AorB[]]
}

type AorB = A | B | Array;

type AorBorArray = AorB;

When used with just A or B values, it works perfectly:

const obj1 = { prop1: 'test' };
const a: A = obj1; // OK

const obj2 = { prop2: 123 };
const b: B = obj2;    // OK

However, when attempting to assign a list to a variable of type AorBorArray, it fails:

const list = {
    list: [
        {prop1: 'test'},
        {prop2: 123}
    ]
};

const assignTest: AorBorArray = list; // Error

https://i.sstatic.net/nDkY2.png

The assignment only works if I explicitly cast the list variable to AorBorArray:

const list = {
    list: [
        {prop1: 'test'},
        {prop2: 123}
    ]
} as AorBorArray;

const assignTest: AorBorArray = list; // OK

If I change the type definition of Array to:

type Array = {
    list: AorB[]
}

it also works. But I prefer to enforce having 2 or more items in the array.

Why is this happening? Since users will provide this object, I want to avoid them having to cast every time. How can I adjust the typings so that the assignment works seamlessly?

Thank you!

Answer №1

When a new const is assigned without a type annotation, typescript will try to infer the type.

In this scenario:

const list = {
    list: [
        {prop1: 'test'},
        {prop2: 123}
    ]
};

The inferred type is:

const list: {
    list: ({
        prop1: string;
        prop2?: undefined;
    } | {
        prop2: number;
        prop1?: undefined;
    })[];
}

This means that the array can contain items with either a prop1 or a prop2.

However, it doesn't automatically know if you want a tuple type like [AorB, AorB, ...AorB[]].

Subsequently trying to assign this to AorBorArray will result in an error as the array does not match the tuple type.

This issue is commonly resolved in two ways.


  1. Specify the variable with the correct type when initializing it.
const list: AorBorArray = {
    list: [
        {prop1: 'test'},
        {prop2: 123}
    ]
};

const assignTest: AorBorArray = list; // valid

This allows typescript to interpret the array literal correctly and validate it as a tuple type.


  1. Use as const to enforce an immutable interpretation of the array, resulting in a tuple type due to its known length.

Prior to this, ensure your type can accommodate an immutable value, which might not be suitable for all cases.

type MyArray = {
    list: readonly [AorB, AorB, ...AorB[]]
}

Then:

const list = {
    list: [
        {prop1: 'test'},
        {prop2: 123}
    ]
} as const;

const assignTest: AorBorArray = list; // valid

Most Typescript developers are cautious with arrays when a tuple type is anticipated. However, this is usually not a significant problem, as passing an array literal directly to a function expecting a tuple will be interpreted correctly without issues.

The only challenge arises when creating the value separately initially without a type annotation and then passing it to a function later on. In such instances, it's advisable to determine the expected type, import it, and annotate the value accordingly as in option 1.

Therefore, there's no need to fret too much about it.

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

Store Angular 17 control flow in a variable for easy access and manipulation

Many of us are familiar with the trick of "storing the conditional variable in a variable" using *ngIf="assertType(item) as renamedItem" to assign a type to a variable. This technique has always been quite useful for me, as shown in this example: <ng-t ...

Using TypeScript with Vue and Pinia to access the store

Is it necessary to type the Store when importing it to TypeScript in a Pinia Vue project? // Component <p>{{ storeForm.firstName }}</p> // receiving an error "Property 'storeForm' does not exist on type" // Store ...

Having trouble getting a constructor to function properly when passing a parameter in

Here's the code snippet I'm working with: import {Component, OnInit} from '@angular/core'; import {FirebaseListObservable, FirebaseObjectObservable, AngularFireDatabase} from 'angularfire2/database-deprecated'; import {Item} ...

Assignment of type 'Angular Promise<void>' is not compatible

In the process of developing a website with Angular4 and retrieving data from Contentful CMS API, I am encountering an issue with assigning proper types to the returned data despite seeing the correct types in the console. The example mock data is as foll ...

Refresh Form Following Submission

When using a react form that triggers a graphql mutation upon button click, the text entered in the form fields remains even after the mutation has been executed. This necessitates manual deletion of text for subsequent mutations to be run. Is there a way ...

AngularTS - Using $apply stops the controller from initializing

Every time I launch the application, the angular {{ }} tags remain visible. Removing $scope.$apply eliminates the braces and displays the correct value. I am utilizing Angular with Typescript. Controller: module Application.Controllers { export class Te ...

Obtaining Axios response header in a Typescript environment

I am currently working on a rest call that may return a header that I need to store. In order to do this, I have to first check if the header is present before storing it. Here is how I approached it: private getHeader(response: AxiosResponse) { if (r ...

Incorporating a swisstopo map from an external source into an Angular

I am looking to integrate a swisstopo map into my angular 8 app. As I am new to angular, I am unsure how to include this example in my component: I have tried adding the script link to my index.html file and it loads successfully. However, I am confused a ...

Create a global variable by importing an external module in TypeScript

I am currently developing a TypeScript npm module called https://www.npmjs.com/package/html2commonmark. This module is versatile and can be utilized in nodejs (via require) as well as in the browser (by loading node_modules/html2commonmark/dist/client/bund ...

Issue encountered with Ionic and ssh2: process.binding is not supported

I am currently delving into the world of Ionic and experimenting with creating a basic application that utilizes SSH2 to establish an ssh connection between the app and a server. Here is a breakdown of the steps I took to encounter the issue: Steps to Rep ...

Is it better to store data individually in localStorage or combine it into one big string?

When it comes to keeping track of multiple tallies in localStorage, one question arises: Is it more efficient to store and retrieve several small data points individually or as one larger chunk? For example: localStorage.setItem('id1', tally1); ...

The 'Promise<void>' type cannot be assigned to the 'Promise<xxx>' type

Attempting the following transaction in TypeScript resulted in a compile error. The error message stated: Type 'Promise<void>' is not assignable to type 'Promise<transactionArgument>'. However, the function returns a value o ...

Having difficulty resolving all parameters for the component: (?, [object Object]) in the Jasmine component Unit Test

While defining a UT for a component with an extended class using i8nService and ChangeDetectionRef, I encountered an error preventing me from instantiating it: Failed: Can't resolve all parameters for BrandingMultiselectComponent: (?, [object Object] ...

Is there a way to access the value of a public variable within the @input decorator of a function type?

I am working on a dropdown component that utilizes the @Input decorator to define a function with arguments, returning a boolean value. dropdown-abstract.component.ts @Input() public itemDisabled: (itemArgs: { dataItem: any; index: number }) => boo ...

How can I limit the input of string values from a Node Express request query?

export type TodoRequest = { order?: 'asc' | 'desc' | undefined; } export const parseTodoRequest = (requestData: ParsedQs): TodoRequest => { return { order: requestData.order as 'asc' | 'desc' | u ...

Having trouble retrieving mobiscroll instance in Angular with Ionic

I'm facing an issue with accessing the instance of my mobiscroll widget in Angular 4 with Ionic Framework. Despite following all the correct steps, the actual widget won't get selected. Below is the code for the widget: <mbsc-widget [options ...

Tips for activating automatic building of packages when utilizing pnpm install?

Our unique project is utilizing a combination of pnpm, workspace, and typescript in adherence to the monorepo standard. Upon cloning the repository, we execute a pnpm install command to download dependencies and establish links between local packages. De ...

How to retrieve a Typescript variable within an ngIf conditional statement in Angular

How can I access a TypeScript variable inside an ngIf statement? TS: public data: string; HTML: <td> <div *ngIf="data === 'BALL' ; else noplay" >{{ play }}</div> <ng-template #noplay> {{ gotohome ...

Exploring the Worldwide Influence of TypeScript, React, and Material-UI

I am currently following an official tutorial on creating a global theme for my app. In my root component, I am setting up the global theme like this: const themeInstance = { backgroundColor: 'cadetblue' } render ( <ThemeProvider theme ...

The Material UI Datagrid is failing to display any data

I'm currently facing a challenge that has left me scratching my head. I've implemented Material UI Datagrids, but for some reason, I can't get it to populate with data, and the reason eludes me. Even though the Component profiler shows that ...