How can I remove a wider type from a union type without removing its subtypes in TypeScript?

Current solution (Typescript 4.1+)

2021 Edit: The 2.8 implementation of KnownKeys<T> is broken since Typescript 4.3.1-rc, but a new, more semantic implementation using key remapping is available since 4.1:

type RemoveIndex<T> = {
  [ K in keyof T as string extends K ? never : number extends K ? never : K ] : T[K]
};

It can then be used as follows:

type KnownKeys<T> = keyof RemoveIndex<T>;

interface test {
  req: string
  opt?: string
  [k: string]: any
}

type demo = KnownKeys<test>; // "req" | "opt" // Absolutely glorious!

Below is the preserved solution for pre-4.1 Typescript versions:


I got a solution from @ferdaber in this GitHub thread.

Edit: Turns out it was, to little fanfare, published in 1986 by @ajafff

The solution requires TypeScript 2.8's Conditional Types and goes as follows:

type KnownKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;

Below is my attempt at an explaination:

The solution is based on the fact that string extends string (just as 'a' extends string) but string doesn't extend 'a', and similarly for numbers. Basically, we must think of extends as "goes into"

First it creates a mapped type, where for every key of T, the value is:

  • if string extends key (key is string, not a subtype) => never
  • if number extends key (key is number, not a subtype) => never
  • else, the actual string key

Then, it does essentially valueof to get a union of all the values:

type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never

Or, more exactly:

interface test {
  req: string
  opt?: string
  [k: string]: any
}
type FirstHalf<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
}

type ValuesOf<T> = T extends { [_ in keyof T]: infer U } ? U : never
// or equivalently, since T here, and T in FirstHalf have the same keys,
// we can use T from FirstHalf instead:
type SecondHalf<First, T> = First extends { [_ in keyof T]: infer U } ? U : never;

type a = FirstHalf<test>
//Output:
type a = {
    [x: string]: never;
    req: "req";
    opt?: "opt" | undefined;
}
type a2 = ValuesOf<a> //  "req" | "opt" // Success!
type a2b = SecondHalf<a, test> //  "req" | "opt" // Success!

// Substituting, to create a single type definition, we get @ferdaber's solution:
type KnownKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;
// type b = KnownKeys<test> //  "req" | "opt" // Absolutely glorious!

Explaination in GitHub thread in case someone makes an objection over there


Per accepted answer: https://stackoverflow.com/a/51955852/714179. In TS 4.3.2 this works:

export type KnownKeys<T> = keyof {
  [K in keyof T as string extends K ? never : number extends K ? never : K]: never
}