JSON Schema vs TypeScript: Runtime vs Compile-Time Validation

Last updated:

TypeScript and JSON Schema both describe data shapes, but at different layers: TypeScript catches type errors at compile time (before the program runs); JSON Schema validates data at runtime (when external data arrives). TypeScript has 0 runtime cost — types are erased after compilation. JSON Schema validation with Ajv adds ~0.5–2 ms per request for typical schemas. The 3 approaches for using both together: (1) write TypeScript interfaces and a matching JSON Schema separately, (2) use Zod to define a schema that generates both a TypeScript type and a runtime validator, (3) use json-schema-to-typescript to generate interfaces from existing JSON Schema files. This guide covers all 3 patterns.

The core difference

TypeScriptJSON Schema
When it runsCompile time (build step)Runtime (your code calls a validator)
What it validatesYour code's type usageExternal data (API bodies, user input)
OutputBuild error or warningPass/fail + error messages
LanguageTypeScript onlyAny language
Validates external inputNo — types are erased at runtimeYes
PortableNo — TypeScript-specificYes — JSON, language-agnostic

Why TypeScript alone is not enough for external data

TypeScript types are erased when the code compiles to JavaScript. At runtime, if an API returns an unexpected shape, TypeScript cannot catch it.

// TypeScript thinks this is safe at compile time
const res = await fetch('/api/user')
const user: User = await res.json()  // No runtime check!
console.log(user.name.toUpperCase()) // Could throw if name is null

TypeScript gives you confidence about code you write. It cannot give you confidence about data that arrives at runtime from an external source.

What JSON Schema adds

JSON Schema validates the actual shape of a value at runtime. If an API response does not match the schema, validation fails with a descriptive error message before your code tries to use the data.

const userSchema = {
  type: 'object',
  required: ['id', 'name', 'email'],
  properties: {
    id:    { type: 'integer' },
    name:  { type: 'string' },
    email: { type: 'string', format: 'email' },
  },
  additionalProperties: false,
}

// Validate before using
const valid = ajv.validate(userSchema, data)
if (!valid) throw new Error(ajv.errorsText())

For schema examples across common patterns, see JSON Schema Examples.

Using both together

The most reliable pattern is to define a JSON Schema for external data, validate at the boundary (API handler, form submit, file read), and then assert a TypeScript type after successful validation.

import Ajv from 'ajv'
const ajv = new Ajv()

interface User {
  id: number
  name: string
  email: string
}

const userSchema = { /* ... */ }
const validateUser = ajv.compile<User>(userSchema)

function parseUser(raw: unknown): User {
  if (!validateUser(raw)) {
    throw new Error(ajv.errorsText(validateUser.errors))
  }
  return raw  // TypeScript now knows this is User
}

Libraries like zod and valibot unify both steps: you define a schema once and get both runtime validation and inferred TypeScript types.

When to reach for each

ScenarioUse
Internal function signaturesTypeScript
Validating an API responseJSON Schema or zod
Validating user-submitted form dataJSON Schema or zod
Documenting an API contract (OpenAPI)JSON Schema
Cross-language validation (Python, Go, Java)JSON Schema
Database record typesTypeScript + ORM types

Generating TypeScript types from JSON Schema

If you already have a JSON Schema, tools like json-schema-to-typescript can generate TypeScript interfaces from it, keeping your types and schema in sync.

Going the other direction — inferring a schema from a sample JSON payload — is useful when you have real data but no schema yet. See the JSON to TypeScript tool for instant interface generation from a sample object.

Validate a JSON Schema online

Test your schema against real data in Jsonic's JSON Schema Validator. Paste a schema and a payload and see validation errors highlighted immediately.

Open Schema Validator

Frequently asked questions

What is the main difference between JSON Schema and TypeScript types?

TypeScript validates at compile time — types are erased when code runs. JSON Schema validates at runtime — when your code receives actual data from an API, file, or user input. TypeScript cannot catch runtime data shape errors from external sources.

When should I use JSON Schema instead of TypeScript?

Use JSON Schema for API request/response validation, form data, configuration files, and webhook payloads. Also use it when you need language-agnostic validation across Python, Go, Java, and other languages. TypeScript alone cannot protect against bad runtime data.

Can I generate TypeScript from JSON Schema?

Yes. The "json-schema-to-typescript" npm package generates TypeScript interfaces from a JSON Schema file, keeping types and schema in sync. "quicktype" also supports this direction. This approach works well in OpenAPI-first development workflows.

Can I generate JSON Schema from TypeScript?

Yes. "typescript-json-schema" and "ts-json-schema-generator" generate JSON Schema from TypeScript interfaces. Alternatively, Zod and Valibot let you define a schema once and automatically infer the TypeScript type, eliminating the need for separate generation.

What is the difference between compile-time and runtime validation?

Compile-time validation catches type errors in your code before it runs. Runtime validation checks the actual values your code processes during execution. TypeScript is compile-time only; it cannot validate data arriving from external APIs at runtime.

Which tools convert between JSON Schema and TypeScript?

Schema to TypeScript: "json-schema-to-typescript", "quicktype". TypeScript to schema: "typescript-json-schema", "ts-json-schema-generator". Unified approach (no conversion needed): Zod, Valibot, and Typia define schema and type together.