What are the types of tuple lookups using mapped types in TypeScript

Since the introduction of mapped tuple types in typescript 3.1, I was eager to see if this code sample would function as expected:

export interface SettingKey {
    General_Language: 'en' | 'sl';
    Map_InitialLongitude: number;
    Map_InitialLatitude: number;
}

export function fetchSetting<K extends (keyof SettingKey)[]>
        (...keys: K): Promise<SettingKey[K]> {
    return null as any;
}
fetchSetting('General_Language', 'Map_InitialLongitude').then(x => {
    return x['General_Language'] === 'de' // would want compilation error 'de' not in 'en' | 'sl'
})

Unfortunately, it did not work as expected. The errors encountered were as follows:

ttt.ts:7:83 - error TS2536: Type 'K' cannot be used to index type 'SettingKey'.

7 export function fetchSetting<K extends (keyof SettingKey)[]>(...keys: K): Promise<SettingKey[K]> {
                                                                                    ~~~~~~~~~~~~~

ttt.ts:11:12 - error TS2571: Object is of type 'unknown'.

11     return x['General_Language'] === 'de'
          ~

The second error seems to be a result of the first error and is not a major concern. However, the first error is the critical one.

The keys are an array of keyof SettingKey, and I expected SettingKey[K] to be an array of types of the specified properties (i.e., in the sample code provided, it should be ['en' | 'sl', number]). According to the pull request introducing the typescript feature:

If T is an array type S[] we map to an array type R[], where R is an instantiation of X with S substituted for T[P].

However, this might only apply to mapped types, and since I am using a lookup type here, it seems like that might be the reason it's not working as expected.

Even though I believe my intention is clear, I am wondering if this can be achieved in a type-safe manner in typescript.

Answer №1

In order to have a mapped tuple, it is essential to have a mapped type that will associate the original tuple (found in the type parameter K) with the new tuple type.

export interface SettingKey {
    General_Language: 'en' | 'sl';
    Map_InitialLongitude: number;
    Map_InitialLatitude: number;
}

type SettingKeyProp<P extends keyof SettingKey> = SettingKey[P]
type SettingKeyArray<K extends { [n: number]: keyof SettingKey }> = {
  [P in keyof K]: K[P] extends keyof SettingKey ? SettingKey[K[P]]: never 
} 
export function fetchSetting<K extends (keyof SettingKey)[]>
        (...keys: K): Promise<SettingKeyArray<K>> {
    return null as any;
}
fetchSetting('General_Language', 'Map_InitialLongitude').then(x => {
    // x[0] is 'en' | 'sl'
    return x[0] === 'de' /// since you want a tuple, you should index by number not name
})

If you prefer to index by name, it is also feasible, but the mapped type should iterate over the values in the array rather than the keys:

type SettingKeyArray<K extends { [n: number]: keyof SettingKey }> = {
  [P in K[number]]: SettingKey[P] 
} 
export function fetchSetting<K extends (keyof SettingKey)[]>
        (...keys: K): Promise<SettingKeyArray<K>> {
    return null as any;
}
fetchSetting('General_Language', 'Map_InitialLongitude').then(x => {
    // you can access by name
    return x.General_Language === 'de' 
}) 

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

Using ES modules with TypeScript, Webpack, and Jasmine: A comprehensive guide

My Package Workflow For my personal projects, I have a consistent structure for the packages I create and reuse. In production, I write these packages in TypeScript and compile them to JavaScript using `tsc` before publishing them to npm. This allows me t ...

Tailor TypeScript to support various JavaScript versions

One of the advantages of TypeScript is the ability to target different versions of Javascript globally - allowing for seamless switching between transpiling ES3, ES5, or ES6. For browsers like IE that require ES3 support, it serves as the lowest common de ...

The file parameter in the upload is consistently undefined in tsoa-swagger

Having trouble with Tsoa nodejs File upload Followed the tsoa documentation for writing the method, but the output variable is consistently undefined This is my current method https://i.sstatic.net/YrNc0.png @Post('/uploadNewExporterTemplate&apos ...

Custom HTML binding in expanding rows of Angular 2 DataTables

I am currently working on implementing a data table feature that allows for an extended child row to be displayed when clicking the + icon. This row will show additional data along with some buttons that are bound via AJAX before transitioning to Angular 2 ...

Explaining the process of defining an object type in TypeScript and the conversion from JavaScript

Currently, I am attempting to enhance the background of a React website developed in typescript (.tsx) by incorporating particles. My approach involves utilizing the particle-bg component available at: https://github.com/lindelof/particles-bg However, whe ...

Excluding properties based on type in Typescript using the Omit or Exclude utility types

I am looking to create a new type that selectively inherits properties from a parent type based on the data types of those properties. For instance, I aim to define a Post type that comprises only string values. type Post = { id: string; title: string ...

The modal popup feature is dysfunctional within the hierarchical component structure of angular-bootstrap-md

In my project, there is a structured hierarchy of components that includes: Agent task-list (utilizing the shared task-list-table component) task-type (as a separate component) preview-task (a modal component) agent.component.html (where task-type, ta ...

An issue occurred in the modal window following the relocation of project files

I encountered an issue with the modal in my Nativescript project after rearranging a few project files, including the modal. I updated the imports and deleted any compiled JavaScript files to ensure that my project could recompile correctly. Although I&ap ...

angular component that takes a parameter with a function

Is there a way for my angular2 component to accept a function as a parameter? Uncaught Error: Template parse errors: Can't bind to 'click' since it isn't a known property of 'input'. (" minlength="{{minlength}}" [ERROR -&g ...

How to refresh a page manually in Angular 2

How can I have a page in Angular reload only once when a user visits it? This is my attempt: In the homepage component, I added the following code: export class HomepageComponent implements OnInit { constructor() { } ngOnInit() { location.relo ...

What steps do I need to take to implement Dispatch in a React Native project?

Context App.tsx import React, { createContext, useContext, useReducer, useEffect, ReactNode, Dispatch, } from "react"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { StateType, ActionType } f ...

Change the boolean value of a checkbox to text before including it in a GET request to send to an API

Currently, I am working on a school project that involves creating a recipe search application using Angular for the frontend and Laravel for the backend. The application fetches recipes from the Edamam API. I am looking to implement a feature where users ...

"Exploring the world of 3rd party libraries in Angular2 with Typescript and Webpack

I've begun working on a fantastic seed project that can be found at: https://github.com/AngularClass/angular2-webpack-starter However, I've encountered an issue with integrating third-party modules. Can anyone offer guidance on how to properly a ...

Utilize mapGetter and mapMutations in Vuex with TypeScript without the need for class-style components syntax

After completing a project in Vue, I found myself getting a bit confused without static types. To address this, I decided to incorporate TypeScript into my project while still maintaining the traditional way of writing code, without classes and decorators. ...

Angular 2 rc1 does not support ComponentInstruction and CanActivate

In the process of developing my Angular 2 application with Typescript using angular 2 rc.1, I've noticed that the official Angular 2 documentation has not been updated yet. I had references to ComponentInstruction Interface and CanActivate decorator ...

The reason for the Jest failure is that it was unable to locate the text of the button

As someone who is new to writing tests, I am attempting to verify that the menu opens up when clicked. The options within the menu consist of buttons labeled "Edit" and "Delete". However, the test fails with the message: "Unable to find an element with te ...

Every time a new message is sent or received, I am automatically brought back to the top of the screen on the

I'm currently working on integrating a chat feature into my Angular Firestore and Firebase app. Everything seems to be functioning well, except for one issue - whenever a new message is sent or received, the screen automatically scrolls up and gets st ...

Typescript subtraction operation may result in Undefined

I am a beginner in the world of TypeScript and I'm currently struggling with running this code snippet: class TestClass { public t: number = 10; constructor() { this.t = this.t - 1; console.log(this.t); } } var obj = new TestClass(); ...

Understanding the limitations of function overloading in Typescript

Many inquiries revolve around the workings of function overloading in Typescript, such as this discussion on Stack Overflow. However, one question that seems to be missing is 'why does it operate in this particular manner?' The current implementa ...

Issues have been identified with the collapse functionality of the Angular 6 Material Tree feature

Recently, I've been working on creating a tree structure to handle dynamic data using Angular material tree component. In order to achieve this, I referred to the code example mentioned below: https://stackblitz.com/edit/material-tree-dynamic Howeve ...