Different varieties of TypeScript's keyof when working with objects

I am grappling with the concept of TypeScript's types when incorporating the keyof type operator on objects. Check out this example:

type TypeA = { [k: number]: boolean };
type AKey = keyof TypeA;
//   ^? type AKey = number

type TypeB = { [k: string]: boolean };
type BKey = keyof TypeB;
//   ^? type BKey = string | number

The TypeScript documentation includes a note that reads:

Note that in this case, keyof { [k: string]: boolean } results in string | number — because JavaScript object keys are always coerced to a string, making obj[0] equivalent to obj["0"].

This can be quite puzzling as it may seem contradictory. The expectation is for AKey to be number (due to coercion to a string), and BKey to simply be a string since numbers aren't permitted.

In addition, the behavior differs when using Record<>, possibly due to the usage of in versus ::

type Record<K extends string | number | symbol, T> = { [P in K]: T; }

type TypeC = { [k in number]: boolean }; // Record<number, boolean>
type CKey = keyof TypeC;
//   ^? type CKey = number

type TypeD = { [k in string]: boolean }; // Record<string, boolean>
type DKey = keyof TypeD;
//   ^? type DKey = string

All types allow the use of both numbers and strings as keys, implying that the type definitions do not impact this aspect:

const value: TypeA | TypeB | TypeC | TypeD = {
  0: false,
  "1": true,
};

If you can shed some light on this confusion surrounding types, I would greatly appreciate it!

Answer №1

When the keyof operator is used with mapped types or types containing index signatures, its behavior is detailed in the pull request found at microsoft/TypeScript#23592. Here are the rules:

For an object type X, keyof X behaves as follows:

  • If X has a string index signature, keyof X includes string, number, and symbol-like properties,
  • If X has a numeric index signature, keyof X includes number, and string-like and symbol-like properties,
  • keyof X covers string-like, number-like, and symbol-like properties.

In TypeScript, numeric keys of type number are considered a subtype of keys of type string to support array indexing; however, object keys are essentially numeric strings. An object with a numeric index signature doesn't necessarily support every string key, while one with a string index signature does. This distinction is important for understanding how keys are treated.


The difference between keyof Record<string, T> being string and keyof {[k: string]: T} being string | number remains consistent even after the pull request implementation. The reasoning behind this may not be definitive, but developers can refer to discussions in microsoft/TypeScript#31013 for more insights.

The choice of syntax results in different behaviors when it comes to mapped types. To delve deeper into this, explore #23592

Therefore, the consistency of keyof {[P in K]: T} returning K even if K is string highlights the importance of syntax choices in defining behaviors. This discrepancy with keyof {[k: string]: T} underscores the versatility provided by varying syntax options, ultimately leading back to microsoft/TypeScript#23592.

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

Ways to determine the number of duplicate items in an Array

I have an array of objects that contain part numbers, brand names, and supplier names. I need to find a concise and efficient way to determine the count of duplicate objects in the array. [ { partNum: 'ACDC1007', brandName: 'Electric&apo ...

Need to import Vue component TWICE

My question is simple: why do I need to import the components twice in the code below for it to function properly? In my current restricted environment, I am unable to utilize Webpack, .vue Single File Components, or npm. Despite these limitations, I mana ...

Instructions for adding a new property dynamically when updating the draft using immer

When looking at the code snippet below, we encounter an error on line 2 stating Property 'newProperty' does not exist on type 'WritableDraft<MyObject>'. TS7053 // data is of type MyObject which until now has only a property myNum ...

Is there a way to trigger an image flash by hovering over a button using the mouseover feature in AngularJS2?

When I hover over the 'click me' button, I want an image to appear on the webpage. When I stop hovering, the image should disappear using the mouseover option. This is what I attempted in my app.component.ts and my.component.ts files: Here is t ...

Unable to transfer object from Angular service to controller

I am currently utilizing a service to make a $http.get request for my object and then transfer it to my controller. Although the custom service (getService) successfully retrieves the data object and saves it in the responseObj.Announcement variable when ...

Is it possible to access your app directly from the browser without requiring any user prompts?

After successfully setting up my app for both android and ios with the necessary app link and universal link, I am now focusing on redirecting users from a specific website to my app. The mobile aspect is all set, but I need to work on the browser/server s ...

Customizable column layout in react-grid-layout

I am looking to implement a drag and drop feature in which users can place items into a grid without fixed columns. The goal is that if an item is dragged from outside the grid boundaries and dropped to the right (an empty area), the grid will automaticall ...

Service in Angular2 designed to retrieve JSON data and extract specific properties or nodes from the JSON object

Currently, I am trying to teach myself Angular2 and facing some difficulties along the way. I am working on creating a service that loads a static JSON file. Right now, I am using i18n files as they are structured in JSON format. The service will include a ...

Strategies for iterating over an array in React with TypeScript

I'm currently working on looping through an array to display its values. Here's the code I have: ineligiblePointsTableRows() { return this.state[PointsTableType.INELIGIBLE].contracts.map(contract => { return { applied: (&l ...

When you type a letter in the middle of a string, the cursor is automatically moved back to the end - Material UI

I designed a ChipInput feature that switches to a string when focused and transforms into a Chip component when blurred, with chips separated by commas. Everything seems to be functioning properly except for one issue I am encountering. Whenever I type in ...

Obtain the popup URL following a fresh request using JavaScript with Playwright

I'm having trouble with a button on my page that opens a popup in a new tab. I have set up a listener to capture the URL of the popup when it opens: page.on('popup', async popup => { console.log('popup => ' + await pop ...

The Kubernetes cluster unexpectedly closes down following a period of processing

My GCP cluster is hosting a NodeJS server. The server functions flawlessly when run locally, but mysteriously stops without any error messages when I attempt to send a post request to a specific route. This post request is supposed to trigger the sending o ...

Utilizing SCSS variables

Currently, I am in the process of developing an Angular 4 application using angular-cli and have encountered a minor issue. I am attempting to create a component that has the ability to dynamically load styling. The ComponentX component needs to utilize a ...

Issue TS2769: No matching overload found for this call. The type 'string | null' cannot be assigned to type 'string | string[]'

export class AuthService { constructor(private http: HttpClient, private webService: WebRequestService, private router: Router) { } login(email: string, password: string) { return this.webService.login(email, password).pipe( shareReplay(), ...

Each Tab in Ionic2 can have its own unique side menu that opens when selected

In my ionic2 app, I wanted to implement a unique side menu for each of my tabs. Here is what I attempted: I used the command ionic start appname tabs --v2 to create the initial structure. Next, I decided to turn both home.html and contact.html (generated ...

What is the reason behind the possibility of assigning the exported class to a variable within Ionic 2?

The code snippet below can be found in settings.ts: @Component({ selector: 'page-settings', templateUrl: 'settings.html' }) export class SettingsPage { } } Similarly, in the app.component.ts file, we are able to assign the Clas ...

Mastering Typing for Enhanced Order Components using Recompose and TypeScript

I have been working on integrating recompose into my react codebase. As part of this process, I have been experimenting with getting some basic functionality to work. While I have made progress, I am uncertain if I am following the correct approach for usi ...

What is preventing me from including an additional parameter in a function in TypeScript?

I am currently developing a task management application. I am facing an issue while attempting to incorporate the event and items.id into a button function for actions like delete, edit, or mark as completed. While this functionality works smoothly in pla ...

Instructions on how to present a list of employee information according to the user's gender preference using a selection of three radio buttons

I have developed a view that displays a table of employees, using a json array to store their details in the component. Additionally, I have implemented 3 radio buttons: all, male, and female. My goal is to have the table show all employees when "all" is ...

Inefficiency in POST method prevents data transmission to MongoDB

I've developed a MERN application and now I'm testing the backend using the REST client vscode extension. This is how it looks: `POST http://localhost:4000/signup Content-Type: application/json { "email": "<a href="/cdn-cgi ...