TypeScript Type Casting: as, Angle Brackets and Safe Casts

If you’ve tried to write input as number on a string, you’ve probably seen error TS2352 and wondered why TypeScript type casting refuses. The fix: use as to describe data, and Number() to change it.

That’s because a TypeScript cast is only a note to the compiler. It never touches the value at runtime, so it can’t turn the text “149.99” into a real number for you.

In this tutorial, I’ll show you TypeScript type casting five ways on one small order payload, plus what to do when you need a class instance. I ran every example with TypeScript 7.0.2 and Node.js v22.22.2 in strict mode.

The Sample Order Payload We’ll Cast

Every example uses the same order, the way it arrives from an API: a JSON string. Here’s the raw text:

const raw = '{"id": 4821, "customer": "Megan Bowen", "total": 149.99}';
  • id is a number, 4821.
  • customer is a string, Megan Bowen.
  • total is 149.99. A second payload later sends it as the string “89.50”, which is the tricky row.

JSON.parse() returns any, so TypeScript knows nothing about this object. Casting is how you tell it what the shape is, and that string total shows why a cast alone isn’t always enough.

Method 1: Cast With the as Keyword (Recommended)

Use as whenever you know more about a value’s type than the compiler does. Parsed JSON, a fetch response and a value from localStorage are the usual cases.

Same order payload, now cast to an Order interface. Here’s the code:

interface Order {
  id: number;
  customer: string;
  total: number;
}

const raw = '{"id": 4821, "customer": "Megan Bowen", "total": 149.99}';

const order = JSON.parse(raw) as Order;
console.log(order.customer);
console.log(`Total: $${order.total.toFixed(2)}`);

Output:

Megan Bowen
Total: $149.99
VS Code editor and terminal showing TypeScript type casting of JSON.parse output to an Order interface with the as keyword
The terminal prints Megan Bowen and a total of $149.99 after tsc compiles the cast with no errors.

It prints Megan Bowen and Total: $149.99.

How does this code work?

JSON.parse(raw) gives back any. Adding as Order changes the type of order to Order, so you get autocomplete on order.customer and a compile error if you mistype order.totl.

The JavaScript that tsc emits is just JSON.parse(raw). The as Order part is erased, which is why the TypeScript docs call this a type assertion rather than a cast.

Important: as trusts you completely. If the API sends a different shape, nothing warns you. I cover the full keyword, including as const, in the TypeScript as keyword guide.

Method 2: Cast With Angle-Bracket Syntax

Angle brackets are the older way to write the same assertion. You’ll still see them in code written before TypeScript 1.6, so it helps to recognize them.

Same payload again, this time with <Order> in front of the value:

interface Order { id: number; customer: string; total: number }

const raw = '{"id": 4821, "customer": "Megan Bowen", "total": 149.99}';

const order = <Order>JSON.parse(raw);
console.log("Order", order.id, "for", order.customer);

Output:

Order 4821 for Megan Bowen

The output is Order 4821 for Megan Bowen, identical to the as version.

How does this code work?

<Order>JSON.parse(raw) and JSON.parse(raw) as Order compile to the same JavaScript. The brackets just sit in front of the expression instead of after it.

So here’s the catch: angle brackets clash with JSX. In a .tsx file TypeScript reads <Order> as a tag, so the syntax isn’t allowed there. That’s why I’d stick with as everywhere.

Why Does TypeScript Reject Casting a String to a Number?

TypeScript only allows an assertion when one type could plausibly be the other. A string can never be a number, so the compiler stops you:

const input = "149.99";        // a price read from a form field

const price = input as number;

Output:

cast_error.ts(3,15): error TS2352: 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.
VS Code red squiggle and Problems badge on a TypeScript cast from string to number with error TS2352
The cast line gets a red squiggle, and tsc reports TS2352: the conversion of string to number may be a mistake.

tsc fails with TS2352 and suggests going through unknown first. That error is protecting you, because the value really is text.

Method 3: Double Casting Through unknown

A double cast (as unknown as T) forces any assertion through. Use it only for test mocks or a library type you know is wrong.

Same price string as before, cast twice:

const input = "149.99";        // a price read from a form field

const price = input as unknown as number;

console.log(typeof price);
console.log(price + 10);

Output:

string
149.9910
VS Code terminal showing a TypeScript double cast through unknown where typeof still prints string and adding 10 concatenates
It compiles cleanly, yet typeof prints string and price + 10 prints 149.9910 instead of a sum.

The compiler is happy, but typeof price is still string, and price + 10 prints 149.9910.

How does this code work?

unknown accepts any value and can be asserted to any type, so the two steps get past TS2352. If you want the difference between the two top types, read any vs unknown in TypeScript.

At runtime nothing happened, so the plus sign did string concatenation. That’s the bug a double cast hides from you.

Method 4: Convert Types at Runtime With Number(), String() and Boolean()

When the value has the wrong runtime type, don’t cast it, convert it. JavaScript’s built-in functions return a new value of the type you need.

Same price string, plus a quantity of “3” from the cart form:

const input = "149.99";
const qty = "3";

const price = Number(input);
const units = parseInt(qty, 10);
console.log(typeof price, typeof units);
console.log(`Line total: $${(price * units).toFixed(2)}`);

const label = String(4821);
console.log(label, typeof label);

console.log(Number("abc"), Boolean(""), Boolean("no"));

Output:

number number
Line total: $449.97
4821 string
NaN false true
VS Code terminal output of TypeScript converting strings to numbers with Number and parseInt and a number to a string with String
Both values print as number, the line total is $449.97, and String() turns 4821 into a string.

You get number number, a line total of $449.97, and 4821 string.

How does this code work?

Number("149.99") parses the whole string, and parseInt("3", 10) reads an integer. Both give real numbers, so multiplying works. String(4821) goes the other way.

The last line shows the edge cases: Number("abc") is NaN, an empty string is false, and any other text, even “no”, is true. For more on that, see converting a string to a boolean in TypeScript.

Pro Tip: check Number.isNaN() after converting user input. For number formatting, the guide to convert a number to a string in TypeScript covers toFixed() and template literals.

Method 5: Cast Safely With a Type Guard

A type guard checks the value at runtime, then narrows its type for you. It’s the right choice for anything that comes from outside your code.

Now the sample grows to two payloads. The second one has the string total “89.50”:

interface Order { id: number; customer: string; total: number }

function isOrder(value: unknown): value is Order {
  if (typeof value !== "object" || value === null) return false;
  const o = value as Record<string, unknown>;
  return typeof o.id === "number"
    && typeof o.customer === "string"
    && typeof o.total === "number";
}

const payloads = [
  '{"id": 4821, "customer": "Megan Bowen", "total": 149.99}',
  '{"id": 4822, "customer": "Alex Wilber", "total": "89.50"}',
];

for (const raw of payloads) {
  const data: unknown = JSON.parse(raw);
  if (isOrder(data)) {
    console.log(`Order ${data.id}: $${data.total.toFixed(2)}`);
  } else {
    console.log("Rejected:", raw.slice(0, 38) + "...");
  }
}

Output:

Order 4821: $149.99
Rejected: {"id": 4822, "customer": "Alex Wilber"...
VS Code terminal showing a TypeScript type guard accepting a valid order and rejecting a payload with a string total
The guard accepts order 4821 at $149.99 and rejects the Alex Wilber payload, whose total is a string.

The first order prints Order 4821: $149.99. The second is rejected instead of slipping through as a fake number.

How does this code work?

isOrder returns value is Order, a type predicate. Inside the if, TypeScript treats data as Order with no as at the call site.

The one small assertion left, as Record