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?
CodePudding user response:
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 how you want 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
; or even[T[K]] extends [U] ? [U] extends [T[K]] ? K : never : never
CodePudding user response:
You can extract the types that match your criteria, and then use the keyof operator to get the keys of those types.
export type KeysOfType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
type Object = {
id: string;
optional?: boolean;
union: boolean | string;
show: boolean;
};
type BooleanKeys = KeysOfType<Object, boolean>; // 'show' | 'optional' | 'union'
type StringKeys = KeysOfType<Object, string>; // 'id' | 'union'
You can also use the Record <K,V> to get it, it's a built-in type that creates a new object type with a set of properties K of type V, like below:
export type KeysOfType<T, U> = keyof Record<keyof T, T[keyof T] extends U ? T[keyof T] : never>;