Typing transformations in TypeScript

in #eos4 years ago

Typing transformations in TypeScript

I really enjoy using TypeScript and use it in all of my own projects. There's just something about knowing what types your objects and functions are, and the great code completion in VSCode, that I always miss when working on a JavaScript project.

One big annoyance for me is when I have a certain type of an object and I want to create a copy of that type where some fields have a different type.
I run into this a lot when receiving a type from a third-party library or an API request and I want to process and transform this object to make it easier to work with. As an example, consider an API endpoint that returns this type:

type ApiReponse = {
  foo: number;
  bar: string;
  veryBigNumber: string; // overflows JS Number type, so it's encoded as a string by the API endpoint
  date: string; // as ISOString "2020-05-30T11:39:40.230Z"
}

In my code I want to work with a JavaScript Date object instead of an ISOString, so in my API fetch function I do this transformation:

type TransformedApiReponse = ?

const request = async ():Promise<TransformedApiReponse[]> => {
  const result = await fetch<ApiReponse[]>(url)
  return result.map(response => ({
    ...response,
    veryBigNumber: new BigNumber(response.veryBigNumber),
    date: new Date(response.date),
  }))
}

While TypeScript allows you to easily extend an object type using the & { newKey: string; } construct, it's hard to change an existing type.
Or so I thougt.
I used to copy and paste the existing ApiRepsonse type and then change the date field. However, if the type comes from a third-party library that's not possible and there's an easier much better way using omit, one of TypeScript's utility types:

// before
type TransformedApiReponse = {
  foo: number;
  bar: string;
  veryBigNumber: BigNumber; // changed
  date: Date; // changed
}

// now
type TransformedApiReponse = Omit<ApiReponse, "date" | "veryBigNumber"> & {
    veryBigNumber: BigNumber;
    date: Date;
}

type New = Omit<ApiReponse,"date" | "veryBigNumber">
  & {
      veryBigNumber: BigNumber;
      date: Date;
  }

First, we use Omit to remove the date and veryBigNumber field from the ApiReponse type and then we extend the resulting type using the new type values.

This way, we only specify the change set for the type instead of the whole type, and if the API endpoint's response type changes we only need to update the original ApiReponse type.

Have a look at some of the other TypeScript utility types - they can be very useful.
These are the ones I use most often:

Omit, Pick, Partial, ReturnType

Originally published at https://cmichel.io/typing-transformations-in-type-script/