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?
- TypeScript project? Use Zod. One schema gives you validation and types, and it fits naturally with the OpenAI and Vercel AI SDKs.
- Python project? Use Pydantic v2. It is the idiomatic choice, FastAPI speaks it natively, and
@field_validatorcovers the custom rules a schema cannot express. - Multi-language, or a public API? Use JSON Schema as the single source of truth, and generate or hand-write the Zod and Pydantic versions from it. The schema travels; the code stays local to each stack.
- Unsure? Start with JSON Schema. It is the format the providers consume, so it is never wasted work — and StrictJSON generates the matching Zod and Pydantic code from the same description, so you can switch later without rewriting anything.
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 →