sindresorhus / type-fest

A collection of essential TypeScript types
Creative Commons Zero v1.0 Universal
14.25k stars 541 forks source link

Question: Is it possible to get compile error with "Get"? #194

Open ozum opened 3 years ago

ozum commented 3 years ago

Hi,

Thanks for the types. I was looking for a type-safe version of lodash.get with string paths, and Get solves my problem greatly.

Is it possible to get a compile error if the wrong path is provided?

Example

interface Person {
  name: string;
  address: {
    street: string;
    no: number;
  };
}

const get = <BaseType, Path extends string>(object: BaseType, path: Path): Get<BaseType, Path> =>
  lodash.get(object, path);

const person: Person = { name: "Jane", address: { street: "Abbleton", no: 12 } };

const street = get(person, "address.street"); // Works like a charm.
const unknown = get(person, "address.xyz"); // Is it possible to get an error with this?

Many thanks again,

Upvote & Fund

Fund with Polar

ozum commented 3 years ago

I realized, that my request is out of the scope of this type, and I searched for a solution. In case anyone looking for the same, combining Get with the solution below solves my problem:

https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object/58436959#58436959

Here is an example:

CAVEAT: There is a heavy performance penalty, even D is set to 3 levels deep.

interface ApiResponse {
  hits: {
    hits: Array<{
      _id: string
      _source: {
        name: Array<{
          given: string[]
          family: string
        }>
        birthDate: string
      }
    }>
  }
}
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]
type Join<K, P> = K extends string | number ? P extends string | number ? `${K}${'' extends P ? '' : '.'}${P}` : never : never
type Paths<T, D extends number = 3> = [D] extends [never] ? never : T extends object ?
  { [K in keyof T]-?: K extends string | number ?
        `${K}` | Join<K, Paths<T[K], Prev[D]>>
    : never
  }[keyof T] : ''

const get = <BaseType, Path extends Paths<BaseType>>(object: BaseType, path: Path): Get<BaseType, Path> =>
  lodash.get(object, path);

const apiResponse: ApiResponse = {} as any
const result = get(apiResponse, 'hits.hits.2')

Of course, any performant suggestion is greatly appreciated.