Skip to Content
Type JSON from fetch Without as MyType Everywhere
Intermediate
0 likes
Tutorials

Type JSON from fetch Without as MyType Everywhere

Replace scattered casts with one assertion function and one typed fetch helper. After this, a 404 or a changed payload fails with a clear message instead of carrying a wrong type.

You have fetch calls spread over a project, and every one ends in as User, as Todo or as Order. Those casts are promises nobody checks. This page replaces them with one check per type, written once and reused. It is a refactoring recipe. The reasoning behind it is in the course lesson on typing fetch JSON.

Step 1: find the casts

Search your project for as right after .json(). A typical hit looks like this.

type Todo = { id: number; title: string; completed: boolean };

async function main() {
  const response = await fetch("https://jsonplaceholder.typicode.com/todos/99999");
  const todo = (await response.json()) as Todo;
  console.log(todo.title.toUpperCase());
}

main().catch((error) => console.log(error.message));
Cannot read properties of undefined (reading 'toUpperCase')

The server answered 404 with an empty object. The cast gave it the type Todo anyway, and the program fails on the next line with an error that does not mention the request.

Step 2: write an assertion function once

An assertion function checks a value and throws if it is wrong. Its return type, asserts value is Todo, tells the compiler that when the function returns normally, the value is a Todo.

type Todo = { id: number; title: string; completed: boolean };

function assertTodo(value: unknown): asserts value is Todo {
  const item = value as Partial<Record<keyof Todo, unknown>> | null;
  if (
    typeof item !== "object" ||
    item === null ||
    typeof item.id !== "number" ||
    typeof item.title !== "string" ||
    typeof item.completed !== "boolean"
  ) {
    throw new Error("Response is not a todo");
  }
}

const good: unknown = JSON.parse('{"id":1,"title":"Buy pens","completed":false}');
assertTodo(good);
console.log(good.title.toUpperCase());

const bad: unknown = JSON.parse("{}");
try {
  assertTodo(bad);
} catch (error) {
  console.log((error as Error).message);
}
BUY PENS
Response is not a todo

After assertTodo(good), the compiler treats good as a Todo, and good.title is allowed with no cast. With {}, the function throws, so the code after it never runs with a wrong value.

Step 3: put the fetch and the check behind one function

type Todo = { id: number; title: string; completed: boolean };

function assertTodo(value: unknown): asserts value is Todo {
  const item = value as Partial<Record<keyof Todo, unknown>> | null;
  if (
    typeof item !== "object" ||
    item === null ||
    typeof item.id !== "number" ||
    typeof item.title !== "string" ||
    typeof item.completed !== "boolean"
  ) {
    throw new Error("Response is not a todo");
  }
}

async function getTodo(id: number): Promise<Todo> {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
  if (!response.ok) {
    throw new Error(`Request failed with status ${response.status}`);
  }
  const data: unknown = await response.json();
  assertTodo(data);
  return data;
}

async function main() {
  console.log((await getTodo(3)).title);
  await getTodo(99999).catch((error) => console.log(error.message));
}

main();
fugiat veniam minus
Request failed with status 404

Now the status is checked, the body is checked, and the rest of your code gets a Todo it can trust. Replace every as Todo in the project with a call to getTodo, and delete the casts.

⚠ Warning: The check only covers the fields you test. If the type has ten fields and the function tests three, the other seven are still a promise. Test every field you read.

Common mistakes

  • Writing the function but still casting at the call site.
  • Skipping response.ok. A 500 with a JSON error body would pass a loose check.
  • Testing that the value is an object and nothing else. An empty object passes.

Related course: TypeScript Intermediate.

About

Replace scattered casts with one assertion function and one typed fetch helper. After this, a 404 or a changed payload fails with a clear message instead of carrying a wrong type.

Responsible
Mary Q
Last Update
10/20/2026
Reading Time
3 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.