How to convert a string to a number in TypeScript

How to convert a string to 
a number in TypeScript
Photo by Gerrie van der Walt

You're writing a TypeScript application and you hit an error like this:

Argument of type 'string' is not assignable to parameter of type 'number'.(2345)

This could happen for a number of reasons. For example, I often run into this when reading a value from an input field. I intend the user to input a number in a form field field but the value I get back is a string (even when the input is type="number"!).

Here's an example:

// user input value
const birthYear: string = '12345'

function isMillenial(birthYear: number) {
    return birthYear < 1980 || birthYear > 2000
}

const millenial = isMillenial(birthYear /* error! */)

To fix this I need to add a conversion that converts a string into a number. This is known as typecasting, or type coercion.

TypeScript is a superset of JavaScript which means all JavaScript applications are also valid TypeScript applications. This means the most straight forward way to fix this is to use methods that JavaScript provides us.

To coerce a string into a number, we can wrap our value in one of several functions: parseInt, parseFloat, or Number (this stackoverflow answer has a great breakdown of the differences).

For our birthYear example, we want parseInt:

const birthYear: string = '12345'
const birthYearNum: number = parseInt(birthYear)

TypeScript has a built-in type definition for parseInt which you can inspect in many IDEs by hovering over the function call. It looks like this:

function parseInt(string: string, radix?: number | undefined): number

Notably the return type is always a number. If we update our example, the error goes away!

// user input value
const birthYear: string = '12345'
const birthYearNum: number = parseInt(birthYear)

function isMillenial(birthYear: number) {
    return birthYear > 1980 || birthYear < 2000
}

const millenial = isMillenial(birthYearNum /* no more error! */)

Error handling

When dealing with user input we should assume we could receive any string. It's possible we receive something that can't be parsed into a valid number:

const birthYear: string = 'abcd'
const birthYearNum: number = parseInt(birthYear) // NaN!

Here parseInt will not throw an error but return a value of NaN (or, Not a Number). In TypeScript (and JavaScript) typeof NaN === 'number'. So, even though we are dealing with an invalid input TypeScript will not give us a type error. Our code will need to handle this explicitly which we can do with the Number.isNaN function:

const birthYear: string = 'abcd'
const birthYearNum: number = parseInt(birthYear) // NaN!

if (Number.isNaN(birthYearNum)) {
  throw new Error('Invalid birthYear!')
  // or, show an error to the user
}

A caution on using TypeScript type assertions

TypeScript provides a (seemingly) helpful feature with the as keyword. This feature is known as type assertion.

Rather than using something like parseInt you might be tempted to do the following:

const millenial = isMillenial(birthYear as number)

TypeScript will complain about this and suggest a workaround:

Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. (2352)

Which would reasonably lead you to implement that workaround:

const millenial = isMillenial((birthYear as unknown) as number)

However, it's important to keep in mind that TypeScript compiles down to JavaScript to produce an executable program. In doing so, all type-related information is compiled away. You're effectively left with this:

const millenial = isMillenial(birthYear)

Even though we used as to tell TypeScript to treat birthYear as a number, the behavior of our program doesn't actually change. All you've really done is tell TypeScript: "I know what I'm doing better than you do."

There are some complex scenarios where this might be useful (which we will save for another day). However, you should strive to produce the correct type using proper runtime type conversion methods (like parseInt) whenever possible. Otherwise you are opting out of the biggest benefits of TypeScript.