json-schemazodpydanticvalidationschema

JSON Schema vs Zod vs Pydantic: Choosing a Validation Layer

·9 min read·JSON Schema

Three tools, one job, different worlds

"Validate this data against a shape" is one of the most common tasks in software, and three tools dominate: JSON Schema (language-neutral, declarative), Zod (TypeScript-first, code-as-schema), and Pydantic (Python-first, class-as-schema). They overlap enough to feel interchangeable and differ enough that picking wrong creates duplicate sources of truth. Here's how to choose — and how to convert between them so you don't maintain three.

JSON Schema: portable and declarative

JSON Schema *is* JSON, which is its superpower: it's the one format every language, API tool, and database can read. OpenAPI, config validators, form generators, and LLM structured-output modes all speak it.

json
{
  "type": "object",
  "properties": { "email": { "type": "string", "format": "email" }, "age": { "type": "integer", "minimum": 0 } },
  "required": ["email"]
}

Strengths: universal, tool-agnostic, the lingua franca for cross-system contracts and LLM schemas. Weakness: it's data, not code — no type inference, and verbose to write by hand.

Zod: schema and TypeScript type in one

Zod defines schemas as TypeScript code and infers the static type from them, so validation and types never drift:

ts
import { z } from "zod";

const User = z.object({
  email: z.string().email(),
  age: z.number().int().min(0).optional(),
});
type User = z.infer<typeof User>;   // { email: string; age?: number }

Strengths: best-in-class DX in TypeScript, one source of truth for runtime checks and compile-time types, powers the Vercel AI SDK. Weakness: it's TypeScript-only — a Zod schema can't be handed to a Python service or a generic API tool.

Pydantic: Python's class-based validator

Pydantic does for Python what Zod does for TypeScript — the model *is* the schema, with editor autocompletion and runtime validation:

python
from pydantic import BaseModel, EmailStr, Field

class User(BaseModel):
    email: EmailStr
    age: int | None = Field(default=None, ge=0)

Strengths: ubiquitous in Python, especially AI tooling (Pydantic AI, Instructor, FastAPI). Weakness: Python-only, like Zod is TS-only.

How to choose

  • Crossing language or system boundaries (APIs, configs, LLM structured output, storing the schema itself)? JSON Schema.
  • Building a TypeScript app or LLM pipeline? Zod.
  • Building in Python? Pydantic.

The key insight: JSON Schema is the interchange format; Zod and Pydantic are the ergonomic front-ends. Both can *emit* JSON Schema (z.toJSONSchema(), model_json_schema()) and be *generated from* it — so you keep one canonical schema and derive the others.

Keep one source of truth

  1. Treat a JSON Schema as canonical for anything shared across languages or sent to an LLM.
  2. Generate the code layer: convert JSON to a Zod schema for TS, or to Pydantic/Python for Python.
  3. Validate real data against the canonical schema in CI so all three stay honest.

For LLM-specific angles see Zod for AI and Validate LLM JSON Against a Schema.

Frequently asked questions

Use JSON Schema when the shape crosses language or system boundaries (APIs, configs, LLM outputs). Use Zod in TypeScript and Pydantic in Python for the best developer experience and type inference within one language.

Yes. Zod (z.toJSONSchema()) and Pydantic (model_json_schema()) both emit JSON Schema, and you can generate Zod or Pydantic code from a JSON Schema. Keep JSON Schema canonical and derive the others to avoid drift.

Because it's data, not code — no static type inference and verbose to author. Zod and Pydantic give you runtime validation *and* editor/compiler types in one definition, which is far better DX inside a single language.

All three appear: providers accept JSON Schema directly, the Vercel AI SDK uses Zod, and Pydantic AI/Instructor use Pydantic. They all ultimately send a JSON Schema to the model — the library just generates it for you.

Try JSON to Zod / Python

Generate Zod or Pydantic from one canonical JSON so all three stay in sync.