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)