Last updated:
Schema Format Converter
Convert between JSON Schema, TypeScript, GraphQL, Zod, Pydantic, and Go structs. 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 — no data is sent to any 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 you can paste schemas that contain customer field names or unreleased product domains without touching a network.
When to use the Schema Converter
Adopting Zod on a TypeScript frontend that already has JSON Schema. You have an OpenAPI spec for the backend; the frontend has been using any at the API boundary. Convert the OpenAPI component 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. Convert them to JSON Schema, then to Go structs, and you have starting types for the new service in the right shape. The converter handles JSON tags and 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 small TypeScript interface for an application user:
interface User {
id: string;
email: string;
displayName: string;
role: 'admin' | 'editor' | 'viewer';
age?: number;
tags: string[];
} Pasted into the converter with TypeScript as the source and JSON Schema as the target, this becomes a JSON Schema with detected formats and reasonable constraints:
{
"$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" } }
}
}
Notice what the converter inferred. The id string was annotated as a UUID format because the field name is id and the typical convention; email got the email format from the field name; displayName picked up reasonable string-length bounds. These inferences are visible in the panel and overridable — when the inference is wrong (your id is actually a Stripe-style prefixed string, not a UUID), you can adjust the constraint before the next conversion. The age field is marked optional in JSON Schema by being absent from the required array, and it picked up an integer type with a sane bound rather than allowing any number.
Switch the target to Zod. The same JSON Schema becomes a Zod object schema with chained refinements that map one-to-one with the JSON Schema constraints, plus a z.infer type alias so you get the static TypeScript type back at the bottom:
import { z } from 'zod';
export const UserSchema = 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 User = z.infer<typeof UserSchema>; Switch to Python Pydantic. The same intermediate JSON Schema produces a Pydantic v2 BaseModel with field validators, the camelCase-to-snake_case translation that Python convention requires, and an alias mapping to keep wire-format compatibility with the original JavaScript field names:
from typing import Literal, Optional, List
from uuid import UUID
from pydantic import BaseModel, EmailStr, Field
class User(BaseModel):
id: UUID
email: EmailStr
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]
All three outputs derive from the same canonical JSON Schema. If you tighten maxLength on displayName in the source, the constraint propagates to all targets in the next conversion — that is the value of running everything through a single intermediate.
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 preserves the distinction across the trip.
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 normalizes all of these to the JSON Schema oneOf representation and re-emits the right pattern per target.
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 flags this when it detects integer fields with explicit ranges that exceed the GraphQL Int bounds.
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?
allOf is merged into a single object schema where possible (the typical pattern of base + extension); oneOf and anyOf become discriminated unions in TypeScript and Zod, union types in Pydantic, and interfaces with common fields in GraphQL. Discriminator hints in OpenAPI are honored when present, otherwise the converter falls back to structural unions.
What happens with $ref pointers that resolve outside the pasted schema?
External pointers are surfaced as a warning and replaced with an inline "unknown" or "any" placeholder so the rest of the conversion still produces output. 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?
Yes, with a forward declaration where the target language requires it. TypeScript and Zod use lazy references through z.lazy(); Pydantic uses ForwardRef and model_rebuild(); GraphQL handles circular types natively. The converter detects the cycle, emits the right pattern for each target, and adds a comment showing where the cycle was.
Will a round-trip from one format back to itself be lossless?
Lossy by design. Going TypeScript → JSON Schema → TypeScript will preserve types, optionality, and structure but will not preserve TypeScript-only constructs like conditional types, mapped types, or template literal types. The same applies to Zod refinements and Pydantic validators — they exist below the schema level and cannot survive the trip 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.