Created
February 8, 2025 01:16
-
-
Save okaybeydanol/8ad2a4218b733d931a553cddd5ba3bf8 to your computer and use it in GitHub Desktop.
TypeScript: Extract Nested Object Keys with Depth Limit (With and Without Infer)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Generates a tuple of numbers from 0 up to (but not including) N. | |
| * Used internally for numeric operations like subtraction. | |
| * | |
| * Example: | |
| * type Numbers = Enumerate<3>; // [0, 1, 2] | |
| */ | |
| type Enumerate< | |
| N extends number, | |
| Acc extends number[] = [], | |
| > = Acc['length'] extends N ? Acc : Enumerate<N, [...Acc, Acc['length']]>; | |
| /** | |
| * Subtracts two numbers at the type level. | |
| * Used internally for limiting the depth of nested paths. | |
| * | |
| * Example: | |
| * type Result = Subtract<5, 2>; // 3 | |
| */ | |
| type Subtract<A extends number, B extends number> = Enumerate<A> extends [ | |
| ...Enumerate<B>, | |
| ...infer R, | |
| ] | |
| ? R['length'] | |
| : 0; | |
| /** | |
| * Recursively extracts nested keys of an object as dot-notation strings with a depth limit. | |
| * Useful for representing nested object paths in a string format with a maximum depth. | |
| * | |
| * Example: | |
| * type Theme = { colors: { primary: string; secondary: { light: string; dark: string } } }; | |
| * type Paths = NestedKeyPathsWithDepth<Theme['colors'], 2>; | |
| * "primary" | "secondary.light" | "secondary.dark" | |
| */ | |
| export type NestedKeysPathsWithDepth<T, Depth extends number = 1> = [ | |
| Depth, | |
| ] extends [0] | |
| ? never // Base case: if depth is 0, stop recursion | |
| : T extends object | |
| ? { | |
| [K in keyof T & (string | number)]: T[K] extends Record<string | number, unknown> | |
| ? Subtract<Depth, 1> extends 0 | |
| ? never // Stop recursion if depth limit is reached | |
| : `${K}.${NestedKeysPathsWithDepth<T[K], Subtract<Depth, 1>>}` // Recursive case: dive deeper | |
| : never; // Invalid case: neither string nor object | |
| }[keyof T & (string | number)] | |
| : never; | |
| /** | |
| * Recursively extracts nested keys of an object as dot-notation strings with a depth limit using `infer`. | |
| * Useful for representing nested object paths in a string format with a maximum depth. | |
| * | |
| * Example: | |
| * type Theme = { colors: { primary: string; secondary: { light: string; dark: string } } }; | |
| * type Paths = NestedKeyPathsWithDepthInfer<Theme['colors'], 2>; | |
| * "primary" | "secondary.light" | "secondary.dark" | |
| */ | |
| export type NestedKeysPathsWithDepthInfer<T, Depth extends number = 1> = [ | |
| Depth, | |
| ] extends [0] | |
| ? never // Base case: if depth is 0, stop recursion | |
| : T extends object | |
| ? { | |
| [K in keyof T & (string | number)]: T[K] extends infer V | |
| ? V extends Record<string | number, unknown> | |
| ? Subtract<Depth, 1> extends 0 | |
| ? never // Stop recursion if depth limit is reached | |
| : `${K}.${NestedKeysPathsWithDepthInfer<V, Subtract<Depth, 1>>}` // Recursive case: dive deeper | |
| : V extends string | |
| ? `${K}` // Base case: if the value is a string, return the key | |
| : never // Invalid case: neither string nor object | |
| : never; // Invalid case: key is not a string or number | |
| }[keyof T & (string | number)] | |
| : never; |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Two utility types for extracting nested keys as dot-notation strings with a depth limit:
inferfor better type control.Example: