2

How I can generate all paths of an object as String literal's with dot separator with ts-toolbelt's Object.Paths and String.Join as Union type like this?

// Object
const obj = {
  a: 'abc',
  b: 'def',
  c: {
    c1: 'c1',
    100: 'c2',
  }
}
// Type
type dottedType = 'a' | 'b' | 'c.c1' | 'c.100'

I found this solution without ts-toolbelt:

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 Leaves < T, D extends number = 10 > = [D] extends[never] ?
  never :
  T extends object ?
  {
    [K in keyof T] - ? : Join < K,
    Leaves < T[K],
    Prev[D] >>
  }[keyof T] :
  ""

// Object
const obj = {
  a: 'abc',
  b: 'def',
  c: {
    c1: 'c1',
    100: 'c2',
  }
}
// Type
type dottedType = Leaves < typeof obj >
// type dottedType =  'a' | 'b' | 'c.c1' | 'c.100'

How can I simplify this code with ts-toolbelt?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
fullheart
  • 21
  • 1
  • You'd think it'd just be `String.Join, '.'>` but that gets widened to `string`. Curious... – Alex Wayne Mar 09 '22 at 08:50
  • @AlexWayne , exactly I though the same. I think there is a missing step to iterate over the Union types of `Object.Paths`. But I don't know Typescript so well to understand the problem. – fullheart Mar 09 '22 at 08:59

1 Answers1

2

You'd think it'd be this:

type ObjPaths = Object.Paths<typeof obj>
type DottedObjPaths = String.Join<ObjPaths, '.'> // string

But that gets widened to string.

The problem is that for some reason (probably a good reason) Object.Paths returns tuples with optional values. ObjPaths above is reported to be of type:

type ObjPaths = ["a"?] | ["b"?] | ["c"?, "c1"?] | ["c"?, 100?]

Note all the ? declaring those values as optional. We need to remove those optionals to make String.Join happy.

Looks like List.Required does that.

Which we can use like so:

type ObjPaths = Object.Paths<typeof obj>
type NonOptionalObjPaths = List.Required<ObjPaths>
type DottedObjPaths = String.Join<NonOptionalObjPaths, '.'>
// "a" | "b" | "c.c1" | "c.100"

Which seems to do the right thing.

See Playground

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • Wow, really solid solution. Thank you a lot. I tried to merge your lines to a one-line generic type: `export type DottedObjectPath = String.Join>, ".">`. But I get this error? **error TS2344: Type 'NonOptional>' does not satisfy the constraint 'List'.** How I can solve this? – fullheart Mar 09 '22 at 13:44
  • I, uh, I don't know... That's bizarre. :( – Alex Wayne Mar 09 '22 at 19:55