QuestionsTypeScript

Recursive types & type-safe object paths

Recursive TypesHardTypeScript

Write a recursive type. Then build a `Paths<T>` that produces the dotted key paths of a nested object, and a `Get<T, P>` that returns the type at a path.

What it tests

Type-level recursion combined with template literals and indexed access to walk a nested structure.

Approach & answer

A recursive type refers to itself, letting one definition describe arbitrarily nested data — the canonical example is a JSON value: `type Json = string | number | boolean | null | Json[] | { [k: string]: Json }`. The same recursion powers `DeepReadonly`/`DeepPartial` and, more ambitiously, type-safe paths. `Paths<T>` walks the object: for each key K, emit K itself, and if `T[K]` is an object, also emit `${K}.${Paths<T[K]>}` — a template-literal type concatenating the key with the child's paths, recursing until it hits primitives. `Get<T, P>` is the inverse: split the path on `.` with `P extends `${infer Head}.${infer Rest}``, index in with `T[Head]`, and recurse on Rest; a bare key is just `T[P]`. Together they give a `get(obj, 'user.address.city')` that's fully checked — an invalid path is a compile error and the return type is exactly the type at that leaf. Caveats that separate theory from practice: TS limits recursion depth (deeply nested types can hit 'type instantiation is excessively deep'), unbounded or cyclic structures need a depth guard or they won't terminate, and array/number indices need extra handling (`${number}`). This machinery is the core of typed form libraries (react-hook-form), i18n key checkers, and lodash-`get` wrappers.

Use this technique when

Typed lodash-get wrappers, form field paths (react-hook-form), i18n key validation, deep state selectors.

References

ts