{}
StrictJSON
Guide

Zod vs Pydantic vs JSON Schema — which one should you pick?

Once you have a schema for your LLM output, you still have to validate it in code. Three options cover almost every project: Zod for TypeScript, Pydantic for Python, and plain JSON Schema for everything else. They solve the same problem in different languages, with different trade-offs — and the honest answer is that the right pick depends mostly on the stack you already have.

The comparison

Zod 4 Pydantic v2 JSON Schema Draft-07
Language TypeScript / JavaScript Python Language-agnostic (just JSON)
Runtime validation Yes — .parse() throws on bad data Yes — model construction validates Yes, via a validator library
Static typing Inferred — z.infer<typeof Schema> Native — models are typed classes None on its own; needs codegen
Strictness .strict() rejects unknown keys Configurable; extra keys ignored by default additionalProperties: false
Ecosystem Huge in the TS world; pairs with OpenAI SDKs Standard in Python; powers FastAPI Validators exist in every language
Learning curve Gentle — reads like TypeScript Gentle — reads like Python classes Steeper — a separate declarative language

Zod 4 — the TypeScript choice

Zod lets you declare a schema in code and get a TypeScript type for free with z.infer — one definition, runtime validation and static types, always in sync. A typical field reads z.string().email(), an object ends with .strict() to reject unknown keys, and z.enum(["a", "b"]) constrains a value to a fixed set. Its main weakness is portability: a Zod schema only exists inside TypeScript, so it cannot serve as the contract for a Python service or a public API. If your whole pipeline is TypeScript, though, there is little reason to look elsewhere.

Pydantic v2 — the Python choice

Pydantic v2 models are plain Python classes with typed fields, so they feel native in any Python codebase and integrate directly with FastAPI, which uses them for request validation and OpenAPI docs. Constraints live in Field(pattern=...), custom logic in @field_validator, fixed values in Literal, and optional fields in X | None. Two details catch people out: EmailStr requires the separate email-validator package to be installed, and extra keys are ignored by default — set the model config to forbid them if your LLM tends to add fields. Like Zod, Pydantic is tied to one language; a Pydantic model is not a contract another stack can read.

JSON Schema Draft-07 — the portable contract

JSON Schema is not a library; it is a declarative format, a JSON document that describes another JSON document. That makes it the only one of the three that every language can read: the same schema validates in Python, TypeScript, Go, or Rust, and it is the format the providers themselves accept — OpenAI Structured Outputs, Claude tool schemas, and Gemini response schemas are all JSON Schema. The price is verbosity and a steeper learning curve: you write constraints as data (pattern, minimum, required) instead of code, and you get no static types without a code generator. One trap matters in practice: in Python, the popular jsonschema library does not check "format": "email" by default — format assertions are annotations unless you enable a format checker. If an email must really look like an email, use a real pattern instead of relying on format.

So, which one?

Whichever you choose, the rule from our Why Strict JSON? guide still applies: validate the model's output in your own code before it reaches your database. The schema is the contract; the validator is the enforcement.

Generate all three formats in the tool → · Browse the library →