Asserting function return types in chained methods in the middle of the chain in TypeScript

Aug 26, 2024

I recently needed to assert the function return type in the middle of a chained method stack and I was not able to figure out how to do it.

Imagine I have this chained sequence of methods in TypeScript:

const numbers = "1,2,3,undefined,4,5"
  .split(",")
  .filter(Boolean)
  .map((entry) => entry);

I can assert the return type of the last method by adding TS assertion at the end of the chain:

const numbers = "1,2,3,undefined,4,5"
  .split(",")
  .filter(Boolean)
  .map((entry) => entry) as number[];

But I wanted to do it after the .split function, so I tried this:

const numbers = "1,2,3,undefined,4,5"
  .split(",") as (number | undefined)[]
  .filter(Boolean)
  .map((entry) => entry) as number [];

But that is not valid TS syntax, you can’t chain another method assertion like that, okay then I thought that I probably needed parentheses to fix this, so I tried like this:

const numbers = "1,2,3,undefined,4,5"
  .(split(",") as (number | undefined)[])
  .filter(Boolean)
  .map((entry) => entry) as number [];

But this is also not valid TS syntax, then I tried like this:

const numbers = "1,2,3,undefined,4,5"
  (.split(",") as (number | undefined)[])
  .filter(Boolean)
  .map((entry) => entry) as number [];

That is also not a valid TS syntax at this point I was guessing, I thought I knew JS and TS but it turns out that maybe not.

The solution is to place parenthesis correctly and to do that I just have to put the opening parenthesis at the beginning of the whole chain and close it after assertion anywhere in the chain and then I can chain another method to it:

const numbers = ("1,2,3,undefined,4,5"
  .split(",") as string[])
  .filter(Boolean)
  .map((entry) => entry) as number[];

If I need to add another assertion after the filter() function I just have to open the parenthesis at the beginning again:

const numbers = (("1,2,3,undefined,4,5"
  .split(",") as (number | undefined)[])
  .filter(Boolean) as number[])
  .map((entry) => entry) as number[];

No matter how many methods I have in the chain I can always assert between them just by stacking parenthesis at the beginning of the chain and closing wherever I need to:

const numbers = (((("1,2,3,undefined,4,5"
  .split(",") as (number | undefined)[])
  .filter(Boolean) as number[])
  .join(",") as string)
  .split(",") as number[])
  .map((entry) => entry) as number[];

I spent way too much time trying to figure this out and even ChatGTP was not able to give hints to me.