Is it possible to define 'an array containing pairs of values of the same type' in TypeScript?

Each pair must consist of two values of the same type, even though individual pairs may be of different types. For example, [["foo", "bar"], [1, 2]] is valid, but [["foo", 2]] is not. Therefore, using [any, any][] is too generalized.

It's similar to creating a custom type like type X<T> = [T, T] for each element, with a distinct T for every instance. However, X<any>[] would once again be too broad in scope.

Is there a way to achieve this?

(This question simplifies an issue involving instances of a generic interface, often expressed as literals, where TypeScript's assistance in identifying type mismatches within individual objects would be greatly appreciated.)

Answer №1

To handle this type of situation, it is best to use a constrained generic type rather than a concrete type. This means that anything working with such a type will also need to support generics. Typically, you would utilize a helper function to validate whether a value matches the specific type. Here's an example:

type SwapPair<T> = T extends readonly [any, any] ? readonly [T[1], T[0]] : never;

type AsArrayOfPairs<T> = ReadonlyArray<readonly [any, any]> &
    { [K in keyof T]: SwapPair<T[K]> }

const asArrayOfPairs = <T extends AsArrayOfPairs<T>>(pairArray: T) => pairArray;

The SwapPair<T> type transforms a pair type like [A, B] into [B, A]. Thereadonly keyword adds more flexibility but can be removed if needed. Similarly, the AsArrayOfPairs<T> type flips all pair-like properties around, ensuring that the new type matches the candidate type T perfectly if it's a valid array of pairs.

For instance,

AsArrayOfPairs<[[number, number]]>
results in
readonly (readonly [any, any])[] & [readonly [number, number]]
, which is an appropriate match for [[number, number]]. Conversely,
AsArrayOfPairs<[[string, number]]>
yields
readonly (readonly [any, any])[] & [readonly [number, string]]
, indicating that [[string, number]] does not align with the expected structure.

The utility function asArrayOfPairs validates a value without widening it. For example:

const goodVal = asArrayOfPairs([[1, 2], ["a", "b"], [true, false]]);
// const goodVal: ([number, number] | [string, string] | [boolean, boolean])[]

In this case, goodVal is correctly inferred as

Array<[number, number] | [string, string] | [boolean, boolean]>
. On the other hand:

const badVal = asArrayOfPairs([[1, 2], ["a", "b"], [true, false], ["", 1]]);
// error! (string | number)[] is not assignable  

An error is raised due to the problematic entry ["", 1], where the compiler struggles to narrow it down and eventually concludes that it doesn't conform to a valid pair type.


While there are alternative approaches, many straightforward methods may not produce the desired outcome. For example, mapping tuple inference can sometimes result in unexpected widenings, making validation tricky. Therefore, implementing an array swap method post-inference seems to be a more reliable approach.

I hope these insights prove helpful. Best of luck!

Link to code

Answer №2

You might consider defining it in this manner. It may seem a bit complex, but here's one way to approach it:

const mapItems: ([string, string] | [number, number])[] = [['first', 'second'], [3, 4]];

If you have a set number of types in mind, another option is:

type pairs<X, Y> = [X, X] | [Y, Y];

const mapItems: pairs<string, number>[] = [['third', 'fourth'], [5, 6]];

Alternatively, you can use the following method:

type items<T extends [any, any][]> = T;

const mapItems: items<[[string, string], [number, number]]> = [['fifth', 'sixth'], [7, 8]];

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 "Property 'id' is missing on type 'T'" is indicating the absence of the 'id' property in the

I am currently working on a React component that serves as a table and is designed to handle various types of data. The structure of the component is defined as follows: type SimpleTableProps<T> = { data: Array<T>; ... }; const SimpleTabl ...

Attempting to assign the object retrieved from the interface as the new value for window.location.href

I encountered an issue where the error message Type MyInterface' is not assignable to type 'string' popped up. Although I comprehend the problem, finding a suitable solution has proven to be challenging. MyInterface solely returns one item, ...

The Angular 2 Final release is encountering a page refresh error with the message 'Cannot GET' route, signaling that the HashLocationStrategy failed to

After upgrading to the final release of Angular2, I encountered an issue with the HashLocationStrategy. Prior to the update, refreshing the page would fetch the related route with a hash (#) and reload the page. However, post-upgrade, any refreshed page re ...

Can you explain the significance of this particular line in the source code of VSCode?

While browsing through the VS Code source code, I stumbled upon the following snippet: https://github.com/microsoft/vscode/blob/5da4d93f579f3fadbaf835d79dc47d54c0d6b6b4/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts#L166 It appear ...

Cross-origin request error persists despite configuring headers on the server. Unable to successfully relocate image to designated directory on the server

I am encountering a CORS error specifically when sending delete requests from Angular to Laravel. Additionally, I am facing issues with moving car model images to the directory during posting, resulting in errors. I have implemented a CORS middleware and a ...

Retrieve class attributes within callback function

I have integrated the plugin from https://github.com/blinkmobile/cordova-plugin-sketch into my Ionic 3 project. One remaining crucial task is to extract the result from the callback functions so that I can continue working with it. Below is a snippet of ...

Error: Local variable remains undefined following an HTTP request

Whenever I make http calls, my goal is to store the received JSON data in a local variable within my component and then showcase it in the view. Service: getCases(){ return this.http.get('someUrl') .map((res: Response) => res.jso ...

Utilizing constants within if statements in JavaScript/TypeScript

When working with PHP, it is common practice to declare variables inside if statement parenthesis like so: if ($myvar = myfunction()) { // perform actions using $myvar } Is there an equivalent approach in JavaScript or TypeScript?: if (const myvar = myf ...

Choose only the options that are present in both arrays

I am working on creating a multiple select feature that displays all nodes, but only checks the ones that are present in 2 arrays. My front end is developed using Angular 8 and TypeScript. private mountSelect(nodesInRelation, lineApiKey) { console.lo ...

Tips for implementing react-select types in custom component development

Currently, I'm in the process of developing custom components for DropdownIndicator to be used on react-select with Typescript. However, I am encountering difficulties with the component's type due to my limited experience with Typescript. I wou ...

Set the values retrieved from the http get response as variables in an Angular application

Lately, I've been working on a settings application with slide toggles. Currently, I have set up local storage to store the toggle state. However, I now want to update the toggle status based on the server response. The goal is to toggle buttons accor ...

Learn the method for triggering events with a strongly-typed payload in Vue 3 Composition API and TypeScript

I'm currently exploring Vue 3 Composition API along with TypeScript, particularly focusing on emitting events with a strictly typed payload. There's an example provided below, but I'm unsure if it's the most effective way to achieve t ...

Exploring the Possibilities with Material UI v4: Expanding on PaletteColor

Looking to make a simple adjustment in Material UI v4's (v4.11) palette, specifically adding a new field to PaletteColorOptions like darker?: string. The type definition can be found in the relevant Material UI module: createPalette.d.ts export type ...

Using boolean value as default input value in React

When trying to set the default value of a controlled checkbox input from my request, I encountered an error stating "Type 'boolean' is not assignable to type 'string | number | readonly string[] | undefined'". Do you have any suggestion ...

Discovering the ReturnType in Typescript when applied to functions within functions

Exploring the use of ReturnType to create a type based on return types of object's functions. Take a look at this example object: const sampleObject = { firstFunction: (): number => 1, secondFunction: (): string => 'a', }; The e ...

Tips for handling promises in a class getter method in TypeScript

In two of my classes, I use async/await functions to retrieve products from the product class. export default class Products { async getProducts() : Promise<[]> { return await import('../data/products.json'); } } export defa ...

Is there a more efficient method in Typescript to define the types of an object containing arrays?

Suppose I have an object that contains various roles, each granting a specific set of rights: const allRoles = { user: [ 'right1' ], admin: [ 'right1', 'right2' ], } as const If I want to define the types ...

The correct way to incorporate a global property into a component template (using Vue 3, Vite, TypeScript, and the Composition API)

The component's property is not functioning properly https://i.sstatic.net/qaUG9.png src/main.ts import { createApp } from 'vue' import languagePlugin from '@/plugins/languagePlugin' import App from './App.vue' const a ...

Add a service to populate the values in the environment.ts configuration file

My angular service is called clientAppSettings.service.ts. It retrieves configuration values from a json file on the backend named appsettings.json. I need to inject this angular service in order to populate the values in the environment.ts file. Specific ...

What is the best way to change the `this` type of an object that is provided as a parameter to a function

I am looking to create a custom function that can expose certain properties to the this of an object being passed as an argument. For example, this is how the function would be called: const props = ['bar']; myBarFunction(props, { get foo() { ...