Saltar al contenido
Obtener un valor por una ruta profunda tipada

    ↑↓ mover · ⏎ abrir · esc cerrar

    Patrones

    ¿Cómo leo `user.address.city` mediante una ruta reutilizable sin perder el tipo del resultado?

    Genera recursivamente las tuplas de claves válidas y consume una clave cada vez para calcular el valor final.

    La receta

    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')

    La compilación compila esto y comprueba cada resultado de abajo.

    Cómo funciona

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

      El tipo mapeado emite una tupla por cada ruta de propiedad alcanzable.

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

      El resolutor sigue la primera clave y recorre recursivamente el resto de la tupla.

    Lo que obtienes

    Dónde falla

    Los esquemas muy profundos pueden alcanzar el límite de recursión y los arrays exponen más claves de las que suele querer una API de rutas. Añade un límite de profundidad o tratamiento especial de arrays en modelos grandes.

    Conclusión

    Representa una ruta como tupla cuando cada segmento cambia el tipo del siguiente.

    Véase también

    Patrones