Return union of keys from a object with given type

I’m trying to extract a union of the keys of a certain type within an object.

With my best effort, I only made it this far.

export type KeysOfType<T, U> = { [k in keyof T]: T[k] extends U ? k : never }[keyof T];

With that code, given Config and passing boolean as the second argument, it returns show as it’s supposed to.

type Config {
    id: string;
    show: boolean;
};

KeysOfType<Config, boolean>  // <-- Result: ('show')

But I can’t get it to work correctly when the field is optional or based a union

export type Config = {
    id: string;
    optional?: boolean;
    union: boolean | string
    show: boolean;
};

KeysOfType<Config, boolean>  // <-- Result: ('show' | undefined). Expected: ('show' | 'optional' | 'union').

KeysOfType<Config, string>   // -< Result: ('id' | undefined). Expected: ('id' | 'union')

KeysOfType<Config, boolean | undefined>  // <-- Result: ('optional' | 'show' | undefined). Expected: ('optional')

The result I’m expecting from that would be 'show' | 'optional' | 'union' or at least not having undefined as there’s no key called undefined

How can I achieve this?

>Solution :

As far as I can tell from your requirements, inverting the extends clause and removing the optional modifier (-?) from your index type should get you the results you want:

export type KeysOfType<T, U> = {
  [K in keyof T]-?: U extends T[K] ? K : never
}[keyof T];

export type Object = {
    id: string;
    optional?: boolean;
    union: boolean | string
    show: boolean;
};

type X = KeysOfType<Object, boolean>; // "optional" | "union" | "show"
type Y = KeysOfType<Object, string>; // "id" | "union"
type Z = KeysOfType<Object, boolean | undefined>; // "optional" | "union" | "show"

If you want to use unions, I think you do need to decide in which direction you need the matching to work, i.e. whether it’s

  • U extends T[K] ? K : never; or
  • T[K] extends U ? K : never; or
  • U extends T[K] ? T[K] extends U ? K : never : never

Leave a Reply