Skip to content
Get a value by a typed deep path

    ↑↓ move · ⏎ open · esc close

    Patterns

    How do I read `user.address.city` through a reusable path without losing the result type?

    Generate the valid key tuples recursively, then consume one key at a time to compute the value at the end.

    The recipe

    type Paths<T> = T extends object
      ? { [K in keyof T]-?: [K] | [K, ...Paths<T[K]>] }[keyof T]
      : []
    
    type PathValue<T, P extends readonly PropertyKey[]> =
      P extends readonly [infer K, ...infer Rest]
        ? K extends keyof T
          ? Rest extends readonly PropertyKey[]
            ? PathValue<T[K], Rest>
            : never
          : never
        : T
    
    function getPath<T, const P extends readonly PropertyKey[]>(
      value: T,
      ...path: P & Paths<T>
    ): PathValue<T, P> {
      let current: unknown = value
      for (const key of path) {
        current = (current as Record<PropertyKey, unknown>)[key]
      }
      return current as PathValue<T, P>
    }
    
    type Model = {
      user: { address: { city: string }; active: boolean }
    }
    
    declare const model: Model
    const city = getPath(model, 'user', 'address', 'city')

    The build compiles this and checks each result below.

    How it works

    1. 01
      type Paths<T> = T extends object

      The mapped type emits one tuple for every reachable property path.

    2. 02
      PathValue<T[K], Rest>

      The resolver follows the head key and recurses through the remaining tuple.

    What you get

    Where it goes wrong

    Very deep schemas can hit the compiler recursion limit, and arrays expose far more keys than most path APIs intend. Add an explicit depth cap or special array handling for large production models.

    Takeaway

    Represent a path as a tuple when every segment changes the type of the next segment.

    See also

    Patterns