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
; orT[K] extends U ? K : never
; orU extends T[K] ? T[K] extends U ? K : never : never