Why Object.keys returns string[] in TypeScript
Introduction
TypeScript has some non-intuitive issues with reasonable underlying factors, such as Object.keys
returning string[]
for any object, which relates to the characteristics of JavaScript itself and TypeScript’s use of a structural type system. This article explores the underlying reasons and solutions.
Does Object.keys
only return string[]
?
If you look at TypeScript’s es5.d.ts, you’ll find that the return type of Object.keys
is defined as string[]
.
/** * Returns the names of the enumerable string properties and methods of an object. * @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. */keys(o: object): string[];
This means that when we write the following code, we encounter an error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'. No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'.
const person = { name: 'joe', age: 20,};
// Object.keys(person) is string[]// key is stringObject.keys(person).forEach((key) => { console.log(person[key]);});
Analyzing the error message reveals that person
needs to accept specific values for key
rather than any string
. So what is the underlying reason for Object.keys
being designed to return a more loosely defined string[]
type?
Underlying Reasons
Because JavaScript objects can “dynamically change their content at runtime.”
To better align with JavaScript’s dynamic characteristics, TypeScript uses a structural type system, which means it focuses on the structure of types rather than their names. If two types have the same structure, they are considered the same type (Duck Typing).
type A = { foo: number; bar: number };type B = { foo: number };
const a1: A = { foo: 1, bar: 2 };const b1: B = { foo: 3 };
const b2: B = a1;const a2: A = b1;
The above Duck Typing example will result in an error: Property 'bar' is missing in type 'B' but required in type 'A'.
This means that if one type contains all the properties required by another type, it can be assigned to that type (as in the b2
example).
If we design Object.keys
as keys<T extends object>(o: T): (keyof T)[]
to return the keys of an object, this type definition may seem correct at compile time, but it can cause issues at runtime. This is because properties of JavaScript objects can be dynamically added or removed, and the actual properties of an object at runtime may not match the type definition T
at compile time. Therefore, TypeScript chooses to return the type string[]
, which more accurately reflects the dynamic nature of JavaScript.
Solution One: Mapped Types
The simplest way to solve this problem is to map the corresponding type, telling TypeScript that I clearly know the type of key
is keyof typeof person
. Although this approach sacrifices the meaning of TypeScript returning string[]
, it is usually sufficient and can quickly solve the problem.
Object.keys(person).forEach((key) => { console.log(person[key as keyof typeof person]);});
Solution Two: Use Built-in Methods
As in the previous example, if you want to access values in an object, you can directly use Object.values
or Object.entries
, which are good choices and do not necessarily require obtaining the corresponding object items through key
.
Object.values(person).forEach((key) => { console.log(key);});
Solution Three: Type Guard
To maximize type safety, we can add type predicates to check if Object.keys
is of a specific type:
const person = { name: 'joe', age: 20,};
function isPersonKey(value: string): value is keyof typeof person { return Object.keys(person).includes(value);}
Object.keys(person).forEach((key) => { if (isPersonKey(key)) { console.log(person[key]); }});
Further Reading
- Why Object.keys Returns an Array of Strings in TypeScript (And How To Fix It) - Matt Stobbs
- Why doesn’t TypeScript properly type Object.keys? - Alex Harri
- Why doesn’t TypeScript properly type Object.keys? (alexharri.com) - Hacker News