Recursive solution for Transform Path Parameters from Strings to Objects
Hi @mattpocock!!
First of all, thanks so much for these materials you put together. I don't think I've become a wizard yet, but going through the topics and exercises has been quite fun and I feel like I've learned a lot from it already!! 🤯 😍
I just finished watching Transform Path Parameters from Strings to Objects. I didn't think to use the S.Split helper, so I came up with a recursive solution that felt easier to reason with (for me that I wrote it, of course! 😂)
Just pasting it here in case you think something like that might help others in the future and curious to know how you think it compares with the proposed solution ❤️
import { Equal, Expect } from '../helpers/type-utils';
type UserPath = '/users/:id';
type UserOrganisationPath = '/users/:id/organisations/:organisationId';
type UserOrganisationTeamPath =
'/users/:id/organisations/:organisationId/teams/:teamId';
// Extracts param by param until the end
type ExtractParams<T> = T extends `${string}:${infer Param}/${infer RestParams}`
? Param | ExtractParams<RestParams>
: T extends `${string}:${infer Param}`
? Param
: never;
type ExtractPathParams<T> = {
[K in ExtractParams<T>]: string;
};
type tests = [
Expect<Equal<ExtractPathParams<UserPath>, { id: string }>>,
Expect<
Equal<
ExtractPathParams<UserOrganisationPath>,
{ id: string; organisationId: string }
>
>,
Expect<
Equal<
ExtractPathParams<UserOrganisationTeamPath>,
{ id: string; organisationId: string; teamId: string }
>
>
];
@jbalsas I like the solution. I too feel its easier to reason with. I struggled with the Split solution twice in a row.
I definitely like the recursive approach better.
type Url = "/home?search=hello&count=100";
type QueryParams<T> = T extends `${string}?${infer QueryParams}`
? QueryParams
: never;
type ExtractQuery<T> =
T extends `${infer Param}=${infer Value}&${infer RestOfQueries}`
? Param | ExtractQuery<RestOfQueries>
: T extends `${infer Param}=${infer Value}`
? Param
: never;
type ExtractQueryParams<T> = {
[K in ExtractQuery<T>]: string;
};
type SearchQueryObject = ExtractQueryParams<QueryParams<Url>>;
Nicely works for extracting query params too
Nice!
I think Split is obviously sort of a more refined version of this recursive approach. I gave the utility approach a try and came up with this, without the key remapping which I guess is sort of the thing that immediately clicked for me but now it's present when I think about this.
import { Equal, Expect } from '../helpers/type-utils';
type UserPath = '/users/:id';
type UserOrganisationPath = 'a/users/:id/organisations/:organisationId';
type UserOrganisationTeamPath =
'/users/:id/organisations/:organisationId/teams/:teamId';
type Split<
T extends string,
Sep extends string
> = T extends `${infer Param}${Sep}${infer Rest}`
? `${Param}` | Split<Rest, Sep>
: `${T}`;
type Trim<
T extends string,
Char extends string
> = T extends `${Char}${infer Rest}` ? Rest : T;
type StartsWith<
T extends string,
Char extends string
> = T extends `${Char}${string}` ? T : never;
type ExtractPathParams<T extends string> = {
[K in Trim<StartsWith<Split<T, '/'>, ':'>, ':'>]: string;
};
type tests = [
Expect<Equal<ExtractPathParams<UserPath>, { id: string }>>,
Expect<
Equal<
ExtractPathParams<UserOrganisationPath>,
{ id: string; organisationId: string }
>
>,
Expect<
Equal<
ExtractPathParams<UserOrganisationTeamPath>,
{ id: string; organisationId: string; teamId: string }
>
>
];