DEV Community

tmhao2005
tmhao2005

Posted on • Edited on

1 1

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)

Heroku

This site is powered by Heroku

Heroku was created by developers, for developers. Get started today and find out why Heroku has been the platform of choice for brands like DEV for over a decade.

Sign Up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay