I have declared a type for extracting params from url:
type ExtractParams<Path extends string> = Path extends `${infer Start}(${infer Rest})`
? ExtractParams<Start> & Partial<ExtractParams<Rest>>
: Path extends `${infer Start}/:${infer Param}/${infer Rest}`
? ExtractParams<Start> & ExtractParams<Rest> & { [Key in Param]: string }
: Path extends `${infer Start}/:${infer Param}`
? ExtractParams<Start> & { [Key in Param]: string }
: {};
ExtractParams
type in the above code takes in a string path
with dynamic route params
and converts them in an object
having key as route param's name
and value as string
. If the route param
is optional
, then the generated object type
will also have that key as optional
and its value
will be string | undefined
.
example of using the type:
type RP1 = ExtractRouteParams<'/courses/:courseId/classes/:classId'>;
// ^? { courseId: string; } & { classId: string }
type RP2 = ExtractRouteParams<'/courses/:courseId/classes(/:classId)'>;
// ^? { courseId: string; } & { classId?: string | undefined }
Then I referenced this question, which gave me a utility type to Merge the generated object types intersection, so that it is cleaner to view the generated object type:
type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
Using the Expand
utility gave me the merged intersection, which makes the type cleaner:
type Params<Path extends string> = Expand<ExtractParams<Path>>;
type X1 = Params<'/courses/:courseId/classes/:classId'>
// ^? { classId: string; courseId: string }
type X2 = Params<'/courses/:courseId/classes(/:classId)'>
// ^? { classId?: string | undefined; courseId: string }
In short the code works as expected, when having optional parameters defined in this manner: a(/:b)
.
I would like to change the above code, so that there should not be too much of repetition in the type declaration
The syntax that is needed is for declaring optional params is only this one: a(/:b)
. If you could add an add-on in the answer that works for multiple optional param syntaxes as well, that will help in future, but not absolutely required at the moment.
For my use-case, path can have multiple optional parameters but will always be atleast separated by a required parameter. Even if solution allows to have multiple optional parameters one after the other, that does not harm me.
Valid examples of path containing optional params:
'/courses(/:courseId)/classes/:classId' - courseId is optional
'/courses/:courseId/classes(/:classId)' - classId is optional
'/courses(/:courseId)/classes(/:classId)' - courseId and classId both are optional
'/courses(/:courseId)(/:classes)(/:classId)' - If solution includes it, then it does not harm me, but this case is not a requirement.
Invalid examples of path containing optional params, I am 100% sure that my codebase won't be using any of these syntaxes, but If solution is easier, then I do not have any issues as long as I am getting params back.
'(/courses/:courseId)/classes/:classId' - 2 slashes will never be a part of optional params
'/courses(/:courseId/classes)/:classId'
Here is the Playground Link