Home > Back-end >  Generate mapped types from const Object
Generate mapped types from const Object

Time:09-22

I have the following object ( this defines the Angular routing / pages. Some have params that can be replaced.):

const ROUTES = {
  PAGE_NO_PARAMS: '/hello/page/two',
  PAGE_R: '/about/:id',
  PAGE_Z: '/page/page/:param/:id',
  PAGE_N: '/who/:x/:y/:z/page',
} as const

Can I create a type of valid page types from this object that ignores the params (:xxx) in the path with some sort of wildcard?

Ie. create a type like below (but using the ROUTES object). I have it already without the wildcard. However, paths with params resolved ie. /about/xxxxx do not meet the type check`

type ValidRoute = '/hello/page/two' | '/about/${string}' | '/page/page/${string}/${string}'| '/who/${string}/${string}/${string}/page'

In summary, I would like to map though each property in ROUTES and create a type that replaces /:any string/ with /${string}

I hope this all makes sense. I'm trying to replace runtime errors with build errors.

CodePudding user response:

You can use some recursive conditional types to process the path. Wen we encounter :name we can replace it with a $string}

type MakeValidRoute<T extends string, R extends string = ''> =
  T extends `${infer Head}/:${infer Name}/${infer Tail}`?
    MakeValidRoute<`/${Tail}`, `${R}${Head}/${string}`>:
  T extends `${infer Head}/:${infer Name}`?
    `${R}${Head}/${string}`:
    `${R}${T}`

Playground Link

We use tail recursive conditional types to improve performance of these types in the compiler

CodePudding user response:

The other answer here is correct. For completeness, this is how I did it:

type SubstParamNames<T extends string, S extends string = string, A extends string = ""> =
  T extends `${infer F}:${infer _}/${infer R}` ? SubstParamNames<R, S, `${A}${F}${S}/`>
  : T extends `${infer F}:${infer _}` ? `${A}${F}${S}` : `${A}${T}`

The SubstParamNames<T, S> type is a tail recursive conditional type that substitutes all colon-delimited path fragments (starts with a ":" and ends with a "/" or the end of the string) in T with the value in S, which defaults to just string.

That gives the following output:

const ROUTES = {
  PAGE_NO_PARAMS: '/hello/page/two',
  PAGE_R: '/about/:id',
  PAGE_Z: '/page/page/:param/:id',
  PAGE_N: '/who/:x/:y/:z/page',
} as const

type Routes = SubstParamNames<typeof ROUTES[keyof typeof ROUTES]>
/* type RP = type Routes = "/hello/page/two" | `/about/${string}` | 
 `/page/page/${string}/${string}` | `/who/${string}/${string}/${string}/page`
*/

And if it matters, you could change string to something else:

type IfItMatters = SubstParamNames<"/ay/:bee/cee/:dee/eee", "XXX">
// type IfItMatters = "/ay/XXX/cee/XXX/eee"

Playground link to code

  • Related