Exploring the relationships between nested tuple types

When exploring the concept of mapped tuple types in TypeScript, the documentation provides an example:

type MapToPromise<T> = { [K in keyof T]: Promise<T[K]> };

type Coordinate = [number, number]

type PromiseCoordinate = MapToPromise<Coordinate>; // [Promise<number>, Promise<number>]

However, what if the original tuple contains nested tuples, and during the mapping process, I want to extract specific values from those inner tuples?

For example:

// Extracting only the numbers from a tuple with mixed data types
type MessyCoordinate = [
    [string, number],
    [string, number]
]

One might expect to achieve this using the following approach:

type Clean<T extends [string, number]> = T[1]

// Why doesn't this work?
type MapToClean<T> = { [K in keyof T]: Clean<T[K]> }

type CleanCoordinate = MapToClean<MessyCoordinate>; // [number, number]

However, this results in the following compiler error:

Type 'T[K]' does not satisfy the constraint '[string, number]'.
  Type 'T[keyof T]' is not assignable to type '[string, number]'.
    Type 'T[string] | T[number] | T[symbol]' is not assignable to type '[string, number]'.
      Type 'T[string]' is not assignable to type '[string, number]'.(2344)

Adding additional constraints or removing the utility type Clean do not seem to resolve the issue:

type MapToClean2<T extends [string, number][]> = { [K in keyof T]: Clean<T[K]> }
// Why doesn't this work?
type MapToClean<T> = { [K in keyof T]: T[K][1] }
// Type '1' cannot be used to index type 'T[K]'.

type CleanCoordinate = MapToClean<MessyCoordinate>; // [number, number]

The question arises whether the expectation of extracting values from nested tuples within the mapping process is flawed, or if there is a specific syntax that needs to be applied to achieve this desired outcome.

Playground link

Answer №1

An issue that stands out is the lack of constraint on the T in MapToClean<T>, which should ideally be restricted to a type with properties of type [string, number]. By adding an appropriate constraint, it can be resolved as follows:

type MapToClean<T extends { [K in keyof T]: [string, number] }> =
    { [K in keyof T]: Clean<T[K]> }

A more subtle problem arises when trying to enforce a constraint that specifically requires T to be an array type:

type MapToCleanOops<T extends [string, number][]> =
    { [K in keyof T]: Clean<T[K]> } // same error

This particular issue is a known bug in TypeScript, as highlighted in microsoft/TypeScript#27995. The compiler fails to recognize that a tuple will be returned when mapping over a tuple with a mapped type. Until it is fixed, a workaround like using the Extract utility type notifies the compiler of the expected property type:

type MapToCleanOkayAgain<T extends [string, number][]> =
    { [K in keyof T]: Clean<Extract<T[K], [string, number]>> } // okay again

type CleanCoordinate2 = MapToCleanOkayAgain<MessyCoordinate>; // [number, number]

Applying Extract can also rectify the original MapToClean without imposing a constraint on T:

type MapToCleanOkayUnconstrained<T> =
    { [K in keyof T]: Clean<Extract<T[K], [string, number]>> } // still okay

type CleanCoordinate3 = MapToCleanOkayUnconstrained<MessyCoordinate>; // [number, number]

However, it is recommended to use a constraint to prevent passing unexpected values:

type BeCareful = MapToCleanOkayUnconstrained<{ a: 1, b: ["", 4], c: false }>;
/* type BeCareful = { a: never; b: 4; c: never; } */

The decision is yours to make.


Wishing you the best with this solution. Good luck!

Access the code on the Playground

Answer №2

If you already know that you will be transforming the MessyCoordinate into the number, why overcomplicate your task?

type MessyCoordinate = [
    [string, number],
    [string, number]
]
type MapToClean<T> = { [K in keyof T]: number }
type CleanCoordinate = MapToClean<MessyCoordinate>; // [number, number]

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

Building a personalized React component poses challenges when working with MUI REACT interfaces

I am looking to develop a unique component that will display two different elements, an icon, and a title. However, I seem to be encountering errors from TypeScript regarding the declaration of my interface. The error message reads: Property 'map&apos ...

Enhancing DOM Elements in a React Application Using TypeScript and Styled-Components with Click Event

I've been working on an app using React, Typescript, and styled components (still a beginner with typescript and styled components). I'm trying to create a simple click event that toggles between which of the two child components is visible insid ...

Having trouble retrieving values from radio buttons in Angular 2 forms

Having trouble displaying the values of radio button inputs in Angular 2 forms. ...

What is the best way to connect Classes and Objects in Angular5?

Picture a study tool with flashcards and different categories. Each category has a title, while each card contains content, is linked to only one category, and is also connected to another card. How can I establish these connections in Angular 5 and Types ...

Troubleshooting Angular modal fade not functioning

I am facing an issue while trying to display a component called "Login", which belongs to the class "modal fade", from another component named "navbar". Despite my attempts to trigger it by calling data-bs-toggle="modal" data-bs-target="#LoginModal" from t ...

What is the best way to restrict the suggested values in a property depending on the value of another property?

I'm working on creating a third interface that depends on the value of properties from two other interfaces, while also introducing a unique third property. I've chosen not to extend the third property from the first two interfaces as it may not ...

In TypeScript, enhancing an interface with additional properties

Currently, I am working on an Angular project and have developed this interface to display some data: export interface UserData { name: string, vorname: string, strasse: string, plz: string, ort: string, handynummer: string, telefonnummer: s ...

Using Typescript: How to access a variable beyond the scope of a loop

After creating an array, I need to access the elements outside of the loop. I am aware that they are not in the scope and using 'this.' before them does not grant access. colIdx = colIdx + this.columns.findIndex(c => c.editable); this.focusIn ...

Exploring various queries in Firestore

Does anyone know if there is a way to create a sentence similar to this one: return this.db.collection('places', ref => ref.where("CodPais", "<>", pais)).valueChanges(); I have tried using != and <> but neither seem to be valid. ...

ngOnInit unable to properly listen to event stream

I've been trying to solve a persistent issue without success. The problem involves the interaction between three key elements: the HeaderComponent, TabChangingService, and TabsComponent. Within the HeaderComponent, there are three buttons, each with a ...

Forwarding parameter data type

I am facing an issue with 2 navigation points leading to the same screen 1. this.router.navigate([this.config.AppTree.App.Module.Details.Path], { state: { data: { id: this.TableId } } }); this.router.navigate([this.config.AppTree.App.Module.Details.Pa ...

The ES6 reduce method is not giving the expected result

In Image 1, the output you will see if you log the final array from Snippet 1. My goal is to transform my array to match the format shown in Image 2. I attempted using lodash's _.uniqBy() method [Snippet 2], but the logged output of the reduce varia ...

Typescript combined with MongoDB models

One common issue I have encountered involves a method used on my repository: async findByEmail(email: string): Promise<User | null> { const user = await UserModel.findOne({ email }); if(!user) return null; ...

Error: The template could not be parsed due to the following issues: Element 'mat-card' is not recognized

Every time I run Karma "ng test" for my project, an error keeps popping up: Error message: Failed - Template parse errors: 'mat-card' is not a known element Interestingly enough, the application seems to work perfectly fine with the mat-card ta ...

Unexpected token in catch clause in React Native TypeScript

Despite having a fully configured React Native Typescript project that is functioning as expected, I have encountered a peculiar issue: Within all of my catch blocks, due to the strict mode being enabled, typescript errors are appearing like this one: htt ...

Diverse behaviors exhibited by an array of promises

I've developed a function that generates an array of promises: async addDefect(payload) { this.newDefect.setNote(payload.note); this.newDefect.setPriority(payload.priority); const name = await this.storage.get(StorageKeys.NAME); ...

Is there a way to incorporate my getter into a computed property?

My Vuex Store is built using Vuex module decorators and I am facing an issue with using a getter for a computed property. Here is my code: @Module export default class WorkoutModule extends VuexModule { _workout: Workout; @Mutation startWork ...

Iterating over an object and inserting values into a JavaScript object using the ascending count as the identifier

Illustration: { Are you a coffee drinker?: yes, Do you like to exercise regularly?: no, How often do you eat out at restaurants?: 3 times a week, What is your favorite type of cuisine?: Italian } Results: {yes: 1, no: 1, 3 time ...

Error: monaco has not been declared

My goal is to integrate the Microsoft Monaco editor with Angular 2. The approach I am taking involves checking for the presence of monaco before initializing it and creating an editor using monaco.editor.create(). However, despite loading the editor.main.j ...

Important notice: It is not possible to assign refs to function components. Any attempt to do so will result in failure. If you intended to assign a ref, consider

My console is showing a warning when I use the nextJs Link component. Can someone assist me in resolving this issue and providing an explanation? Here is the message from the console: https://i.stack.imgur.com/jY4FA.png Below is a snippet of my code: im ...