Skip to content

Instantly share code, notes, and snippets.

@ryangoree
Created July 30, 2025 05:13
Show Gist options
  • Save ryangoree/333c37ce867c026d289c00a038ceabad to your computer and use it in GitHub Desktop.
Save ryangoree/333c37ce867c026d289c00a038ceabad to your computer and use it in GitHub Desktop.
Get a union of all possible keys for `T` without losing type inference for literal keys.
/**
* One of the *explicit* property names of {@linkcode T}, with any `string` or
* `number` index signatures stripped out.
*
* @example
* ```ts
* type A = LiteralKey<{
* [k: string]: any;
* id: string;
* }>;
* // => "id"
*
* type B = LiteralKey<
* | {
* [k: string]: any;
* name: string;
* }
* | { [k: number]: any; 0: number }
* >;
* // => "name" | 0
* ```
*/
export type LiteralKey<T> = T extends T
? keyof {
[K in keyof T as K & Exclude<number | string, K>]: any;
}
: never;
/**
* Get a union of all possible keys for {@linkcode T}, including literal
* properties and any explicit `string` or `number` index signatures.
*
* It distributes over unions and preserves literal keys alongside index
* signatures, ensuring nice type inference for autocompletion.
*
* @example
* ```ts
* type A = IndexableKey<{
* [k: string]: any;
* id: string;
* }>;
* // => "id" | (string & {})
*
* type B = IndexableKey<
* | {
* [k: string]: any;
* name: string;
* }
* | { [k: number]: any; 0: number }
* >;
* // => "name" | 0 | (string & {}) | (number & {})
* ```
*/
export type IndexableKey<T> =
| LiteralKey<T>
| (T extends T
? // Using array types to perform structural comparisons and prevent
// simplification of the union to a single type.
| (string[] extends (keyof T)[] ? string & {} : never)
| (string[] extends (keyof T)[]
? never
: number[] extends (keyof T)[]
? number & {}
: never)
: never);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment