{}JSON-Validator.net
Tutorial · TypeScript · 7 min read

Validate JSON in TypeScript

TypeScript's type system only works at compile time — it cannot validate JSON data at runtime. This guide shows you how to properly validate JSON in TypeScript using type guards and Zod so your runtime data matches the types your compiler checks.

The Wrong Way: Unsafe Type Assertions

The most common mistake in TypeScript JSON handling is using as to cast the result of JSON.parse(). This compiles fine but provides zero runtime safety:

// ❌ UNSAFE — TypeScript trusts you, but doesn't verify at runtime
interface User {
  name: string;
  age: number;
}

const raw = '{"name": "Alice", "age": "thirty"}'; // age is a string!
const user = JSON.parse(raw) as User;

// TypeScript says user.age is a number — but at runtime it's a string
console.log(user.age + 1); // "thirty1" — a string concatenation, not addition

Type Guards – Manual Runtime Checking

A type guard is a function that narrows a type at runtime using actual checks. Write one for each data structure you parse from JSON:

interface User {
  name: string;
  age: number;
}

// Type guard — checks shape at runtime
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "name" in value && typeof (value as any).name === "string" &&
    "age"  in value && typeof (value as any).age  === "number"
  );
}

function parseUser(raw: string): User {
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch {
    throw new Error("Invalid JSON syntax");
  }

  if (!isUser(parsed)) {
    throw new Error("JSON does not match User shape");
  }

  return parsed; // TypeScript now knows this is User
}

// Usage
const user = parseUser('{"name": "Alice", "age": 30}'); // ✓
const bad  = parseUser('{"name": "Alice", "age": "thirty"}'); // throws

Type guards work well for small structures but become verbose for complex nested objects. That's where Zod shines.

Zod – Schema-First Validation

Zod lets you define a schema once and automatically infers the TypeScript type from it. Install: npm install zod

import { z } from "zod";

// Define schema — TypeScript type is inferred automatically
const UserSchema = z.object({
  name:  z.string().min(1),
  age:   z.number().int().nonnegative(),
  email: z.string().email().optional(),
});

type User = z.infer<typeof UserSchema>; // { name: string; age: number; email?: string }

// .parse() — throws ZodError on failure
const user = UserSchema.parse(JSON.parse(rawJson));

// .safeParse() — never throws, returns a result object
const result = UserSchema.safeParse(JSON.parse(rawJson));
if (result.success) {
  console.log(result.data.name); // fully typed
} else {
  console.error(result.error.issues);
  // [{ path: ['age'], message: 'Expected number, received string' }]
}

Validate API Responses with Zod

The pattern for validating external API responses — the most important place to validate in any app:

import { z } from "zod";

const ProductSchema = z.object({
  id:    z.number(),
  name:  z.string(),
  price: z.number().positive(),
  tags:  z.array(z.string()),
});

type Product = z.infer<typeof ProductSchema>;

async function fetchProduct(id: number): Promise<Product> {
  const response = await fetch(`/api/products/${id}`);

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  // response.json() handles syntax check; Zod handles structure
  const data = await response.json();
  return ProductSchema.parse(data); // throws if API shape changed
}

// For non-throwing variant:
async function tryFetchProduct(id: number) {
  const response = await fetch(`/api/products/${id}`);
  const data = await response.json();
  const result = ProductSchema.safeParse(data);
  if (!result.success) {
    console.warn("API response shape mismatch:", result.error.issues);
    return null;
  }
  return result.data;
}

Comparison: Validation Libraries for TypeScript

Library
Bundle size
Type inference
Best for
Zod
~13 kB (min+gz)
Excellent
General purpose, most ecosystem support
Valibot
~1 kB (min+gz)
Excellent
Bundle-sensitive apps, similar API to Zod
Arktype
~10 kB
Excellent
TypeScript-native syntax, ultra-fast validation
Yup
~23 kB
Good
Form validation, older codebases
io-ts
~20 kB
Excellent
Functional programming style (fp-ts ecosystem)

Frequently Asked Questions

Why can't I just use JSON.parse with a TypeScript type cast?+

TypeScript type assertions (as MyType) are compile-time only — they do not perform any runtime checks. If the JSON has the wrong shape, your code will silently operate on incorrectly typed data. For runtime safety, use a validation library like Zod that checks the actual data at runtime.

What is Zod and why use it for JSON validation?+

Zod is a TypeScript-first schema declaration and validation library. You define a schema once and Zod infers the TypeScript type from it — no duplication. When you call schema.parse(data), Zod validates the data at runtime and throws a ZodError with detailed error messages if the data is invalid.

What is the safest way to parse JSON from an API in TypeScript?+

Use a Zod schema with .safeParse() after fetching. Example: const result = MySchema.safeParse(await response.json()); if (!result.success) { handle error } else { use result.data }. This gives you both syntax validation (from JSON.parse inside response.json()) and structural validation (from Zod).

What alternatives to Zod exist for TypeScript JSON validation?+

Popular alternatives include: Valibot (smaller bundle size, similar API), Arktype (ultra-fast, TypeScript-native syntax), Effect Schema (part of the Effect ecosystem), io-ts (functional-style, older but battle-tested), and Yup (popular for form validation with TypeScript support).

What is the difference between Zod .parse() and .safeParse()?+

schema.parse(data) throws a ZodError if validation fails — use it when you want errors to propagate as exceptions. schema.safeParse(data) never throws — it returns { success: true, data } or { success: false, error } — use it when you want to handle errors explicitly without try/catch.

Debug JSON Syntax Without Writing Code

Before validating the structure, make sure the syntax is valid. Paste JSON for an instant check.

Open JSON Validator

Related Guides