Last updated:
Schema Format Converter
Convert JSON Schema, TypeScript, GraphQL, or a JSON example into Zod, Pydantic, Go structs, TypeScript, GraphQL, or JSON Schema. Paste your source format on the left and get the converted output instantly on the right.
How This Works
All conversions use JSON Schema as an intermediate representation. Your JSON Schema input is first parsed into a JSON Schema, then converted to TypeScript Interface output. This hub-and-spoke approach means every format combination works, though direct conversions may produce slightly different results than format-native tooling. All processing happens in your browser — your schemas are never sent to a server.
What this tool does
A modern application stack rarely settles on a single schema format. The API contract lives in OpenAPI, the frontend types live in TypeScript, the runtime validation lives in Zod or Pydantic, the analytics pipeline ingests JSON Schema, and somewhere along the way somebody added a GraphQL gateway. Each of these wants the same shape — a User has an id, an email, a role enum, an optional age — but each demands its own dialect, and keeping six dialects in sync by hand is the kind of work that sounds tractable until the third schema change in a week. This converter does the translation for you.
The architecture is hub-and-spoke. Every input is parsed into JSON Schema as a canonical intermediate, then JSON Schema is emitted in whichever target language you chose. That means adding support for a new format is two converters (in and out) instead of N×N. It also means the conversion is bounded by what JSON Schema can express — Zod refinements, Pydantic validators, and TypeScript conditional types are below that boundary and do not survive the trip. The conversion runs entirely in your browser, so the schemas you paste, with their customer field names or unreleased product domains, never leave it. We record which formats you converted between, with counts such as the number of properties, never the schema.
When to use the Schema Converter
Adopting Zod on a TypeScript frontend that already has JSON Schema. Your backend's payloads are described in JSON Schema; the frontend has been using any at the API boundary. Convert the schemas to Zod, drop the resulting validators into the fetch layer, and pick up runtime validation without rewriting your types.
Migrating a backend from Python to Go. Pydantic models are a structural definition of every payload your service handles, and Pydantic can export them as JSON Schema (model_json_schema()). Convert that JSON Schema to Go structs and you have starting types for the new service in the right shape. The converter adds JSON tags and uses pointer types for optional fields, which removes most of the manual translation.
Introducing GraphQL on top of a REST API. Your existing JSON Schemas describe the shape of every resource. Convert them to GraphQL SDL as a starting point for your schema-first design — the converter handles non-null markers, list wrappers, and enum generation, leaving you to reconcile the GraphQL-specific concerns (interfaces, unions, custom scalars) by hand.
Prototyping a typed model from a JSON example. An external API returns JSON with no schema documentation. Paste a representative response, choose your target format, and you have a working type definition in seconds. The format inference detects emails, URLs, dates, and UUIDs, so the generated types reflect more than just the structural shape.
Walkthrough with a real example
Start with a JSON Schema for an application user, with formats and bounds on the fields that need them:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "email", "displayName", "role", "tags"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"displayName": { "type": "string", "minLength": 1, "maxLength": 80 },
"role": { "type": "string", "enum": ["admin", "editor", "viewer"] },
"age": { "type": "integer", "minimum": 0, "maximum": 130 },
"tags": { "type": "array", "items": { "type": "string" } }
}
}
Paste it into the converter with JSON Schema as the source and Zod as the target. It becomes a Zod object schema whose chained checks map one-to-one to the JSON Schema constraints (format: "uuid" becomes .uuid(), minLength becomes .min()), plus a z.infer type alias so you get the static TypeScript type back at the bottom. The age field is optional because it's absent from the required array:
import { z } from "zod";
export const rootSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
displayName: z.string().min(1).max(80),
role: z.enum(["admin", "editor", "viewer"]),
age: z.number().int().min(0).max(130).optional(),
tags: z.array(z.string()),
});
export type Root = z.infer<typeof rootSchema>;
Switch to Python Pydantic. The same JSON Schema produces a Pydantic BaseModel with the constraints in Field(), the camelCase-to-snake_case translation that Python convention requires, and an alias that keeps wire-format compatibility with the original JavaScript field name. The uuid and email formats stay plain str fields here:
from pydantic import BaseModel, Field
from typing import List, Literal, Optional
class Root(BaseModel):
id: str
email: str
display_name: str = Field(min_length=1, max_length=80, alias="displayName")
role: Literal["admin", "editor", "viewer"]
age: Optional[int] = Field(default=None, ge=0, le=130)
tags: List[str]
Both outputs derive from the same JSON Schema. If you tighten maxLength on displayName in the source, the next conversion carries the new limit into both. The converter names the root model Root; rename it to suit your code. Starting from a TypeScript interface instead gives you the same fields, but TypeScript has nowhere to put formats or bounds, so add them to the JSON Schema if you need them.
Schema format concepts you should know
Static type systems versus runtime validators. TypeScript interfaces, GraphQL types, and Go structs are static — they exist at compile time and disappear at runtime. JSON Schema, Zod, and Pydantic are runtime — they validate actual values as the program runs. The two solve different problems: static types catch bugs in your code, runtime validators catch bugs in the data crossing your trust boundary. Most production systems need both, which is why the conversion direction matters.
Validation versus typing. A TypeScript interface says "I expect this shape." A Zod schema says "I will reject this if it does not have this shape." Converting from a static type to a runtime validator is mostly safe; converting the other direction loses the validation logic. When in doubt, treat the validator as the source of truth and let the static type be derived from it (the z.infer pattern).
Format-specific features that do not round-trip. Zod's .transform() and .refine() let you express arbitrary JavaScript predicates that JSON Schema cannot represent. Pydantic field_validators and root_validators have the same property. TypeScript conditional types, mapped types, and template literal types are similarly unrepresentable. GraphQL interfaces and unions translate awkwardly into JSON Schema oneOf. Be deliberate about which format owns the source of truth.
Nullability and optionality. These are not the same. A property can be required-and-nullable (the field must be present, and its value may be null), required-and-non-nullable (must be present, must have a real value), optional-and-nullable, or optional-and-non-nullable. JSON Schema expresses both axes; TypeScript merges them when you write x?: string | null; Pydantic distinguishes them via Optional versus a default value. The converter keeps JSON Schema's nullable types ("type": ["string", "null"]) as | null in TypeScript, .nullable() in Zod, and pointers in Go. It doesn't read OpenAPI 3.0's nullable: true yet, and its Pydantic output treats every optional field as nullable, so check nullability in the output.
Discriminated unions. A union of object types with a shared discriminator field (type: "circle" versus type: "square") is a foundational pattern in TypeScript, supported natively in Zod via z.discriminatedUnion(), and modeled in JSON Schema with oneOf plus a const on the discriminator. GraphQL has unions but no discriminator field — the type is implicit in the response. The converter emits oneOf as a plain union (A | B, z.union, Union) without using the discriminator, and its GraphQL output doesn't handle unions yet, so switch to z.discriminatedUnion() or a GraphQL union by hand.
Naming conventions. JavaScript and TypeScript use camelCase. Python uses snake_case. Go uses PascalCase for exported names. The converter respects each ecosystem's convention and emits aliases or tags where needed (Pydantic alias=, Go json:"") so the converted models can still parse data from a system that uses a different convention.
Common mistakes
Expecting Zod refinements to survive a conversion. A .refine(val => val.startsWith("acct_")) is a JavaScript predicate. JSON Schema cannot encode it. After conversion you will need to re-add it as a custom validator in the target format.
Treating GraphQL Int as a 64-bit type. The GraphQL spec defines Int as a 32-bit signed integer. If your data has values above 2,147,483,647, use a custom Long or BigInt scalar; the default Int will overflow silently. The converter maps every integer to Int, so check fields that can exceed that range.
Assuming TypeScript interfaces validate at runtime. TypeScript types are erased before the code runs. A function that takes a User argument will happily accept any object at runtime if the caller cast it. If you need the boundary check, convert the interface to Zod and use the Zod schema to parse the input.
Missing nullable handling differences across targets. GraphQL fields are nullable by default and require an explicit non-null marker. Pydantic fields are required by default and need Optional for nullability. TypeScript splits the two with ? and | null. Mismatching the source's intent against the target's defaults produces converted code that compiles but lies about which fields are required.
Forgetting to apply field-level validators after generating Pydantic models. The converter emits the structural model with constraints expressible in Field(). If the original system had complex validators (cross-field invariants, format checks beyond the standard set), you need to add those back after generation. Treat the output as a starting point, not as a drop-in replacement.
FAQ
Does the converter support allOf, oneOf, and anyOf composition?
Partly. oneOf and anyOf become plain unions in TypeScript (A | B), Zod (z.union), and Pydantic (Union); discriminator hints aren't used, and the GraphQL output doesn't handle them yet. allOf becomes an intersection in TypeScript (A & B) and Zod (.and()); the Pydantic, Go, and GraphQL outputs don't merge allOf members yet, so check those by hand.
What happens with $ref pointers that resolve outside the pasted schema?
They aren't resolved, and there's no warning yet: the output refers to a type named after the last part of the pointer (Product, for shared/types.yaml#/Product), which won't exist. Inline the referenced fragments into the same document before converting, or paste the bundled JSON Schema your build pipeline produces.
Can it handle circular references between schemas?
Not fully yet. TypeScript output handles a type that refers to itself. The Zod and Pydantic outputs refer to a schema before it's defined whenever a definition refers to itself or to one declared later, so they fail when loaded. Until the converter emits z.lazy() and model_rebuild(), reorder the definitions and add those by hand.
Will a round-trip from one format back to itself be lossless?
No. Going TypeScript → JSON Schema → TypeScript keeps a simple interface's property types, string-literal unions, arrays, and optional fields, but the interface is renamed Root, and extends clauses and inline nested object types aren't supported yet. TypeScript-only constructs like conditional types, mapped types, and template literal types don't survive either, and neither do Zod refinements or Pydantic validators: they exist below the schema level and cannot pass through the JSON Schema intermediate.
What is the practical size limit for a schema being converted?
The parser handles schemas with thousands of properties spread across hundreds of definitions. Conversion time scales linearly with the number of properties. The bottleneck is usually the editor's syntax-highlighting on the output, not the conversion itself, so for very large schemas you may want to copy the result to your local editor before scrolling through it.
Related tools and guides
- JSON Schema Validator — once you have a JSON Schema (whether you wrote it directly or generated it via this converter), validate real payloads against it to confirm the schema captures the actual data shape.
- OpenAPI Editor — when the JSON Schema you converted lives inside an OpenAPI document, edit and preview the surrounding spec without leaving the browser.
- Mock Data Generator — generate sample payloads that conform to a converted schema, useful for tests, fixtures, and stubbing out a backend during frontend development.
- Complete Guide to JSON Schema — covers the JSON Schema vocabulary the converter uses as its intermediate representation, including composition, conditional schemas, and reuse patterns.
- API Versioning Guide — when you regenerate types from an updated schema, every consumer feels the change; this guide covers the patterns for evolving them safely.