DEV Community

tmhao2005
tmhao2005

Posted on • Edited on

Typescript override typing for `Object.keys`

If you guys work with Object in Typescript long enough, we all are aware of that one of the most used methods are Object.keys always returning a string[] which should return a list of union types of keys.

Here's how the Object.keys is defined within Typescript's built-in types, you can find it in lib.es5.d.ts:

interface ObjectConstructor { // other stuff /** * 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[]; } 
Enter fullscreen mode Exit fullscreen mode

This is definitely an annoying issue which should be fixed probably later on. So util then, there's a solution for it :)

  • First of all, we define a proper type which returns a list of keys of a generic object:
// some util types type IsAny<T> = 0 extends 1 & T ? true : T; type KnownKeys<T> = { [K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: T[K]; } type IsEmptyObject<T extends Record<PropertyKey, unknown>> = [keyof T] extends [never] ? true : false; type ObjectKeys<T> = IsAny<T> extends true ? string[] : T extends object ? IsEmptyObject<KnownKeys<T>> extends true ? string[] : (keyof KnownKeys<T>)[] : T extends number ? [] : T extends Array<any> | string ? string[] : never; interface ObjectConstructor { keys<T>(o: T): ObjectKeys<T>; } // testing type Test1 = ObjectKeys<{foo: string, bar: string }> // Array<"foo" | "bar"> type Test2 = ObjectKeys<['']> // string[] type Test3 = ObjectKeys<"foo"> // string[] type Test4 = ObjectKeys<Record<PropertyKey, any>> // string[] 
Enter fullscreen mode Exit fullscreen mode
  • Override the built-in ObjectConstructor interface. Let's define object.d.ts in your repo, normally I place the extra typing files under at typings dir. So in my case it's going to be here typings/object.d.ts:
// typings/object.d.ts type IsAny<T> = 0 extends 1 & T ? true : T; type KnownKeys<T> = { [K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: T[K]; } type IsEmptyObject<T extends Record<PropertyKey, unknown>> = [keyof T] extends [never] ? true : false; type ObjectKeys<T> = IsAny<T> extends true ? string[] : T extends object ? IsEmptyObject<KnownKeys<T>> extends true ? string[] : (keyof KnownKeys<T>)[] : T extends number ? [] : T extends Array<any> | string ? string[] : never; // interface can be merged together interface ObjectConstructor { keys<T>(o: T): ObjectKeys<T>; } 
Enter fullscreen mode Exit fullscreen mode
  • Finally, include all defined typing files under typings to the configuration tsconfig.json:
// tsconfig.json { // ... include: [..., "typings"] } 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)