Home > front end >  Return union of keys from a object with given type
Return union of keys from a object with given type

Time:01-18

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; or
  • T[K] extends U ? K : never; or
  • U 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>;
  • Related