5

I'm looking for nested object paths: Is something like this possible?

interface IHuman {
    age: number;
    friend: {
        name: string;
    }
}

keyof IHuman; // "age", "friend.name"
kraftwer1
  • 5,253
  • 10
  • 36
  • 54

4 Answers4

2

v1 - dot path

interface Obj {
  a: {
    x: string
    y: {
      z: string
    }
  }
  b: {
    u: boolean
  }
  c: number
}

type D = DotBranch<Obj> // type D = "c" | "a.x" | "a.y.z" | "b.u"

declare function get(path: DotBranch<Obj>)
get('') // Argument of type '""' is not assignable to parameter of type '"c" | "a.x" | "a.y.z" | "b.u"'.
type DotJoin<A extends string, B extends string> = A extends '' ? B : `${A}.${B}`

type DeepKeys<O extends AnyObject> = {
  [K in keyof O]: O[K] extends AnyObject ? K : never
}[keyof O]

// @ts-expect-error Type 'keyof O' does not satisfy the constraint 'string'.
type DotBranch<O extends AnyObject, P extends string = '', K extends string = keyof O> =
  K extends DeepKeys<O>
  ? DotBranch<O[K], DotJoin<P, K>>
  : /*************/ DotJoin<P, K>

type AnyObject = Record<string, any>

v2 - infer values

See linked gist or playground v2 below to peek under the hood and copy the code.

type DotType<O extends AnyObject, T extends string & DotBranch<O>> = T extends `${infer A}.${infer B}`
  // @ts-expect-error B of type string is not assignable to the constraint
  ? DotType<O[A], B>
  : O[T]

const obj = {
  a: {
    x: 'xx',
    y: {
      z: 'zz',
    },
  },
  b: {
    u: true,
  },
  c: 42,
} as const

type V = DotType<typeof obj, 'c'> // type V = 42 

declare function get<P extends DotBranch<Obj>>(path: P): DotType<Obj, P>

const leaf = getLeaf('a.y.z') // leaf: 'zz'

GitHub Gist
Typescript Playground v2   v1

Qwerty
  • 29,062
  • 22
  • 108
  • 136
1

I don't think this is possible, as for why i can make a wild guess:

interface IHuman {
    age: number;
    'friend.name': any;
    friend: {
        name: string;
    }
}

const b: keyof IHuman = 'friend.name';

friend.name would now be ambigious.

Tom Cumming
  • 1,078
  • 7
  • 9
1

I've not found a nice solution for this too. But you might want to take a look at typed-path project.

The main idea of this project in a single image

skink
  • 5,133
  • 6
  • 37
  • 58
ramlez
  • 21
  • 3
0

It's impossible because there are no string literal type operators in TypeScript. Given the string literals "foo" and "bar", there is no programmatic way to get the string literal "foo.bar" from the type systm. There are several feature suggestions filed in GitHub which might, if implemented, make this possible, like key augmentation or regular-expression string validation. But it doesn't look like they are being actively worked on.

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • 1
    Just a heads up that it is now possible https://github.com/microsoft/TypeScript/pull/40336 – Qwerty May 31 '21 at 22:33