Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keyof inferring string | number when key is only a string

I define an AbstractModel like so:

export interface AbstractModel {    [key: string]: any } 

Then I declare the type Keys:

export type Keys = keyof AbstractModel; 

I would expect that anything with the Keys type would be interpreted univocally as a string, for example:

const test: Keys; test.toLowercase(); // Error: Property 'toLowerCase' does not exist on type 'string | number'. Property 'toLowerCase' does not exist on type 'number'. 

Is this a bug of Typescript (2.9.2), or am I missing something?

like image 881
don Avatar asked Aug 12 '18 11:08

don


People also ask

What does Keyof Typeof do?

What is Keyof typeof TypeScript? keyof is a keyword in TypeScript which is used to extract the key type from an object type.

What does Keyof return?

The keyof operator takes an object type and produces a string or numeric literal union of its keys. A simple usage is shown below. We apply the keyof operator to the Staff type, and we get a staffKeys type in return, which represents all the property names.

How do you give an object a key to type?

Use the keyof typeof syntax to create a type from an object's keys, e.g. type Keys = keyof typeof person . The keyof typeof syntax returns a type that represents all of the object's keys as strings. Copied!


2 Answers

As defined in the Release Notes of TypeScript 2.9, if you keyof an interface with a string index signature it returns a union of string and number

Given an object type X, keyof X is resolved as follows:

If X contains a string index signature, keyof X is a union of string, number, and the literal types representing symbol-like properties, otherwise

If X contains a numeric index signature, keyof X is a union of number and the literal types representing string-like and symbol-like properties, otherwise

keyof X is a union of the literal types representing string-like, number-like, and symbol-like properties.

source

This is because: JavaScript converts numbers to strings when indexing an object:

[..] when indexing with a number, JavaScript will actually convert that to a string before indexing into an object. That means that indexing with 100 (a number) is the same thing as indexing with "100" (a string), so the two need to be consistent.

source

Example:

let abc: AbstractModel = {     1: "one", };  console.log(abc[1] === abc["1"]); // true 

When you only want the string keys, then you could only extract the string keys from your interface like so:

type StringKeys = Extract<keyof AbstractModel, string>;  const test: StringKeys; test.toLowerCase(); // no error 

Also the TypeScript compiler provides an option to get the pre 2.9 behavior of keyof:

keyofStringsOnly (boolean) default false

Resolve keyof to string valued property names only (no numbers or symbols).

source

like image 194
jmattheis Avatar answered Sep 22 '22 06:09

jmattheis


I have faced similar problem. I resolved it by enforcing key to be string:

export type Keys = keyof AbstractModel & string; 

Other option would be to convert key to string: test.toString().toLowercase()

like image 40
madox2 Avatar answered Sep 20 '22 06:09

madox2