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[]; }
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[]
- Override the built-in
ObjectConstructor
interface. Let's defineobject.d.ts
in your repo, normally I place the extra typing files under attypings
dir. So in my case it's going to be heretypings/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>; }
- Finally, include all defined typing files under
typings
to the configurationtsconfig.json
:
// tsconfig.json { // ... include: [..., "typings"] }
Top comments (0)