DEV Community

Daniil Kornilov
Daniil Kornilov

Posted on

TypeScript Passed. Your JSON Is Still Lying.

The compiler is happy. Your API returned {"quantity":"3"}. Adding one produces "31".

The suspicious line can look reassuring:

type Order = { quantity: number };
const order = JSON.parse('{"quantity":"3"}') as Order;
console.log(order.quantity + 1); // "31"
Enter fullscreen mode Exit fullscreen mode

This is a runnable failure example, not an account of a production incident.

TypeScript's handbook explains that type assertions are removed during compilation. They do not introduce runtime validation. The assertion tells the compiler what to assume; it does not convert the incoming string to a number.

Put the check where data enters

Suppose our order contract requires a positive safe integer. Treat the parsed value as unknown, inspect it, then construct the internal object:

type Order = { quantity: number };

function parseOrder(value: unknown): Order {
  if (typeof value !== "object" || value === null ||
      Array.isArray(value) || !("quantity" in value)) {
    throw new Error("Expected an order object");
  }
  const quantity = value.quantity;
  if (typeof quantity !== "number" ||
      !Number.isSafeInteger(quantity) || quantity <= 0) {
    throw new Error("Expected a positive safe integer quantity");
  }
  return { quantity };
}
Enter fullscreen mode Exit fullscreen mode

The function deliberately rejects the string "3". If the API contract permits numeric strings, define and test that conversion separately. Accepting several representations by accident makes malformed input harder to identify later.

Returning a new object also makes the selected fields explicit. This parser ignores additional fields; it does not claim that the entire incoming document matches an exact schema. Decide whether that policy fits your boundary.

Review values that look almost right

For this contract, test the following fixtures:

Input Expected result
{"quantity":3} Accepted
{"quantity":"3"} Rejected
{"quantity":0} Rejected
{"quantity":1.5} Rejected
{}, null, [] Rejected
An integer outside the safe range Rejected

A test that supplies only a correctly typed object never exercises the JSON boundary. It can pass while the network integration remains broken.

Keep the failure observable

If parsing fails, stop before performing the operation that depends on the value. The interface can explain that the response was invalid and provide a recovery path appropriate to that feature.

Avoid turning the error into an empty order or defaulting quantity to one without a product decision. That would hide a data failure behind plausible output.

For a larger schema, use the validation approach already established in your codebase. The useful review question stays the same: which runtime check makes this external value safe to use?

When a coding assistant fixes a type error by adding as, ask it to show the rejected input that justifies the assertion. If the answer contains no runtime check, inspect whether the assertion is only removing a compiler warning.

Where does your application first validate data after JSON parsing?

Top comments (0)