type-transformations-workshop icon indicating copy to clipboard operation
type-transformations-workshop copied to clipboard

Recursive solution for Transform Path Parameters from Strings to Objects

Open jbalsas opened this issue 2 years ago • 2 comments

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 avatar Apr 14 '23 11:04 jbalsas

@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

Viijay-Kr avatar Apr 21 '23 19:04 Viijay-Kr

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 }
    >
  >
];

jbalsas avatar Apr 24 '23 08:04 jbalsas