Skip to content

Instantly share code, notes, and snippets.

@okaybeydanol
Created February 8, 2025 01:11
Show Gist options
  • Save okaybeydanol/3ecc626796becd0fd76c074feec663ad to your computer and use it in GitHub Desktop.
Save okaybeydanol/3ecc626796becd0fd76c074feec663ad to your computer and use it in GitHub Desktop.
TypeScript: Extract Nested Object Keys as Dot-Notation Strings (With and Without Infer)
/**
* Recursively extracts nested keys of an object as dot-notation strings.
* Useful for representing nested object paths in a string format.
*
* Example:
* type Theme = { colors: { primary: string; secondary: { light: string; dark: string } } };
* type Paths = NestedDotNotationPaths<Theme['colors']>;
* "primary" | "secondary.light" | "secondary.dark"
*/
export type NestedDotNotationPaths<T> = T extends object
? {
[K in keyof T & (string | number)]: T[K] extends Record<
string | number,
unknown
>
? `${K}.${NestedDotNotationPaths<T[K]>}` // Recursive case: if the value is an object, dive deeper
: T[K] extends string
? `${K}` // Base case: if the value is a string, return the key
: never; // Invalid case: neither string nor object
}[keyof T & (string | number)]
: never;
/**
* Recursively extracts nested keys of an object as dot-notation strings using `infer`.
* Useful for representing nested object paths in a string format.
*
* Example:
* type Theme = { colors: { primary: string; secondary: { light: string; dark: string } } };
* type Paths = NestedDotNotationPathsInfer<Theme['colors']>;
* "primary" | "secondary.light" | "secondary.dark"
*/
export type NestedDotNotationPathsInfer<T> = T extends object
? {
[K in keyof T & (string | number)]: T[K] extends infer V
? V extends Record<string | number, unknown>
? `${K}.${NestedDotNotationPathsInfer<V>}` // Recursive case: if the value is an object, 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;
@okaybeydanol
Copy link
Author

Two utility types for recursively extracting nested object keys as dot-notation strings:

  1. Direct Access: No infer, simpler structure.
  2. Infer Version: Uses infer for value type extraction.

Example:

type Theme = {
  colors: {
    primary: string;
    secondary: { light: string; dark: string };
  };
};

type Paths = NestedDotNotationPaths<Theme['colors']>; 
// "primary" | "secondary.light" | "secondary.dark"

type PathsInfer = NestedDotNotationPathsInfer<Theme['colors']>; 
// "primary" | "secondary.light" | "secondary.dark"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment