When a variable is declared as keyof T and utilized as a dynamic property, it automatically inferred as a string-index type

I have developed a detailed use-case and created an MCVE to showcase it:

function createSeedMap<T extends (...args:any)=>any>(seedKey:keyof T, seedValue:string):void {
    // ...

    const seedMap:Record<keyof T, string> = {
        [seedKey]: seedValue,
    };

    // ...
}

The crucial element here is the seedMap variable, as the function serves to illustrate the problem at hand.

In this scenario, I have a variable called seedKey with a type of keyof T. This variable acts as a dynamic property in an object initializer assigned to a variable named seedMap, which is typed as Record<keyof T, string>.

It is essential for other sections of my code (not shown) that the type of the seedKey variable remains as keyof T. Changing these types would require numerous type assertions, which I prefer to avoid.

Despite what seems like a straightforward setup, I encountered the following error:

Type '{ [x: string]: string; }' is not assignable to type 'Record<keyof T, string>'.

For reasons I don't fully grasp, the TypeScript compiler interprets my keyof T as a string in the object initializer. The issue arises when trying to assign an object with a string index to a Record with a keyof T index (where seedKey could potentially be a symbol!).

Initially, I attempted to address this by modifying the code as follows:

function createSeedMap<T extends (...args:any)=>any>(seedKey:keyof T, seedValue:string):void {
    // ...

    const seedMap:Record<keyof T, string> = {};

    seedMap[seedKey] = seedValue;

    // ...
}

However, this led to a new error:

Type '{}' is not assignable to type 'Record<keyof T, string>'.

At this point, I suspect that using keyof T as an index for Record may introduce unsupported scenarios.

I've searched for information on this limitation and potential workarounds without resorting to type assertions, but I haven't found a satisfactory solution yet. The related Q/A's I discovered did not seem to directly address this specific issue or remain unanswered:

  • Typescript generic type - Type 'keyof T1' cannot be used to index type
  • How to declare a "Record" type with partial of specific keys in typescript?
  • TypeScript: Indexing into an object type using keyof
  • keyof T doesn't appear as a string subtype within mapped types

Any insights or suggestions would be highly appreciated.

I am running TypeScript 4.8.4 and haveconfirmed these issues in the TS Playground, eliminating any local configuration discrepancies.

Answer №1

Why does keyof T get coerced into a string?

The explanation can be a bit complex. Essentially, the Record interface utilizes a mapped type to define the type:

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

The computation of P in K is extensively explained in the PR Type inference for homomorphic mapped types (#12528).

In summary, TypeScript tries to determine a suitable type for P, and due to the uncertainty in keyof T, it defaults to string.

Possible Solutions

Utilize an actual Map structure

Instead of relying on an object with an index property, consider using a proper JS Map. If necessary, conversion to an object can always be done later:

const seedMap = new Map<keyof T, string>();
seedMap.set(seedKey, seedValue);

const seedRecord = Object.fromEntries(seedMap.entries());

Use a more general Record definition

Rather than specifying keyof T, assign the index type as string|number|symbol:

const seedMap:{ [key:string|symbol|number]: string } = {
    [seedKey]: seedValue,
};

Although this type declaration may not be as precise as keyof T, it seems that a stricter type is not particularly essential based on the provided example.

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

On the subsequent iteration of the function, transfer a variable from the end of the function to the beginning within the same

Can a variable that is set at the end of a function be sent back to the same function in order to be used at the beginning of the next run? Function function1 { If(val1.id == 5){ Console.log(val1.id val1.name) } else{} Val1.id = 5 Val1.name = 'nam ...

Using regular expressions, you can locate and replace the second-to-last instance of a dot character in an email address

I'm looking to replace the second last occurrence of a character in a given string. The length of the strings may vary but the delimiter is always the same. Here are some examples along with my attempted solutions: Input 1: james.sam.uri.stackoverflo ...

Classbased Typescript implementation for managing state with a Vuex store

Hey everyone, I'm currently working on a Vue project with Vuex using decorators for strong typing in my template. As someone new to the concept of stores, I am struggling to understand how to properly configure my store to work as expected in my comp ...

How to have Angular open a PDF file in a new tab

Currently, I am working on implementing the functionality to open a PDF file in a new tab using Angular 9. The PDF file is received from an API as a blob. However, I have encountered an issue due to the deprecation of window.URL.createObjectURL(blob);. Thi ...

Error-throwing constructor unit test

In my code, I have implemented a constructor that takes in a configuration object. Within this constructor, I perform validations on the object. If the validation fails, I aim to throw an error that clearly describes the issue to the user. Now, I am wonde ...

Converting Angular object into an array

Is there a way to extract only the data array from the observable response? I'm interested in retrieving the values of cat_id, cat_name, and cat_description, but not the sql_types array. { "code": 0, "message": "" ...

Possibility for Automatic Type Inference in Generics

Is there a way to have a method infer the type of function parameter without specifying its generic? Currently it is 'GET' | 'POST', but I only need the literal 'GET' const func = <Params, Method extends "GET" | & ...

Finding the perfect pairing: How to align counters with objects?

public counter = 0; x0: any; x1: any; x2: any; x3: any; x4: any; next(){ this.counter += 1; this.storage.set("Count", this.counter); console.log(this.counter); this.logic(); } logic(){ //automatic counter here var xNum = JSON.parse(JSON.stri ...

Discovering the versatility of Typescript objects

I want to define a type that follows this rule: If the property container is present, then expect the property a. If the property item is present, then expect the property b. Both container and item cannot exist at the same time. The code I would expect ...

React - A high-capacity file selector component designed to efficiently handle large numbers of files being selected

I am in search of a component that can retrieve a list of files from the user without actually uploading them. The upload functionality is already in place; I simply require a list of selected files. The component must meet the following criteria: Restric ...

Understanding File Reading in Angular/Typescript

I'm currently developing an app similar to Wordle and I'm facing an issue with reading words from a file. Here's what I tried: import * as fs from 'fs'; const words = fs.readFileSync('./words.txt', 'utf-8'); con ...

Issue TS2322 occurs when an element is not compatible with type ReactElement. Mysteriously, this error appears inconsistently within the application

I have successfully resolved the error mentioned, but I am seeking clarification on why it is occurring in this specific instance and not in other parts of my application. Additionally, I am curious as to why a function and a ternary with the same type sig ...

What is the process for connecting an Angular .ts file with an existing HTML page?

As I finish up the html pages for my website, I find myself in need of integrating Angular to complete the project. My experience with Angular so far has been with ionic apps, where the CLI generates the html, ts, and css pages. However, I am curious if ...

Eliminate the chosen and marked items from a list - Angular 2+/ Ionic 2

Currently, I have a checkbox list on my page. Whenever a user selects the "Save" button, the checked items should be removed from the list and moved to the saved tab that is also displayed. While I have successfully implemented the functionality for removi ...

Navigating an object in TypeScript: the right approach

Curious if there might be a bug in TypeScript? Just seeking clarification on whether my code is incorrect or if there is an actual issue with the language. interface Something { key1: string; key2: number; key3: boolean; } const someObject: S ...

`Browser Extension Compatibility``

I am currently working on developing a TypeScript extension that is compatible with all major browsers. I have come across the package https://www.npmjs.com/package/web-ext-types which I have integrated into my package.json file. While coding in TypeScrip ...

The property you are trying to access is not defined on the enum type in Types

Looking to revise the TypeScript syntax of a lesson found at this link. I am aiming to extract a specific type from a union type using the following syntax: Actions['type'][ActionTypes.FEED_CREATE_POST] The available action types are defined a ...

What steps can I take to fix the ESM / require error while using TypeScript 4.8?

My Node.js application uses TS 4.8, and I recently updated the file-type package. However, after the update, my project compilation fails with the following error: [1] const _fileType = /#PURE/ _interopRequireWildcard(require("file-type")); [1] ...

Expanding ngFor in Angular 2

Is it possible to pass two arguments with ngFor? Here is an example that I would like to achieve: <mat-card *ngFor="let room of arr; let floor of floorArr"> <mat-card-content> <h3>Room Number: {{room}}</h3> <p>Floor ...

The console is displaying a promise that is pending, rather than the desired data

Here is the content of my file: 'use strict' import * as moment from "moment"; import { Report} from "./Report"; import { Timeframe} from "./Timeframe"; import { ReportComparison } from "./ReportComparison"; function test(firstFrom: string, fi ...