TypeScript to JSON Schema Converter
Backend and full-stack developers reach for this tool when an API contract lives in TypeScript interfaces but the validation layer expects JSON Schema — for AJV in a Node service, for an OpenAPI components block, or for a form library like react-hook-form. It also serves teams migrating a codebase from a custom validator to a standard schema format without rewriting type definitions by hand. QA engineers use it to derive test fixtures from the same types the application ships with, keeping tests and production models in sync. Because conversion runs locally, proprietary domain types never leave the browser.
How the Parser Maps TypeScript to JSON Schema
The converter strips comments first (preserving JSDoc blocks separately), then scans for two declaration forms: interface Name { ... } and type Name = .... Each interface body is parsed into a field list where every entry records its name, whether it is optional (the ? marker), its parsed type tree, and any JSDoc tags found immediately before it. Type trees are recursive nodes representing string, number, boolean, null, any, array, tuple, object, union, intersection, literal, Record, or a reference to another named type.
When building the schema, each type node maps to its JSON Schema keyword: string becomes type: "string", a literal union becomes enum, an object node becomes type: "object" with a properties map and a required array listing only non-optional fields, and a reference node emits a $ref pointing into the definitions block. JSDoc tags overlay additional keywords — a @minimum 0 comment attaches minimum: 0 to that property's schema.
Two passes run over the collected types. The first pass builds a schema for every declaration so cross-references resolve regardless of declaration order. The second pass assembles the final document: the first declared type becomes the root schema, and if any $ref was emitted, a definitions object is attached containing every named type. The root's own properties are inlined so the schema is usable standalone while still allowing referenced types to be resolved.
Special-case handling covers nullable unions (T | null folds into a single nullable schema), string literal unions (collapsing to enum), and void/never (mapping to a never-match schema). Depth is bounded to prevent infinite recursion on self-referential types. No network calls are made and no code is evaluated — the parser is a pure function from string to schema object.
Before and After Conversion Examples
Example 1 — Simple interface with optional fields
TypeScript input:
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
sku?: string;
} Generated JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": { "type": "number" },
"name": { "type": "string" },
"price": { "type": "number" },
"inStock": { "type": "boolean" },
"sku": { "type": "string" }
},
"required": ["id", "name", "price", "inStock"]
} Example 2 — Literal unions, arrays, and nested types
TypeScript input:
interface Order {
id: string;
status: "pending" | "shipped" | "delivered";
total: number;
items: Array<{
sku: string;
qty: number;
}>;
customer: {
name: string;
email: string;
};
} Generated JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": { "type": "string" },
"status": { "enum": ["pending", "shipped", "delivered"] },
"total": { "type": "number" },
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"qty": { "type": "number" }
},
"required": ["sku", "qty"]
}
},
"customer": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
},
"required": ["name", "email"]
}
},
"required": ["id", "status", "total", "items", "customer"]
} Example 3 — JSDoc annotations become constraints
TypeScript input:
interface Account {
/**
* User identifier
* @minimum 1
*/
id: number;
/**
* Login email
* @format email
*/
email: string;
/**
* @minLength 8
*/
password: string;
} Generated JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {
"type": "number",
"minimum": 1,
"description": "User identifier"
},
"email": {
"type": "string",
"format": "email",
"description": "Login email"
},
"password": {
"type": "string",
"minLength": 8
}
},
"required": ["id", "email", "password"]
} Where This Converter Fits in Your Workflow
- Generate an AJV validation schema from your domain interfaces without running a CLI in every project.
- Produce a schema for a react-hook-form resolver directly from the interface your components already use.
- Convert a shared config type into JSON Schema so your editor validates config files with autocomplete.
- Build test fixtures by checking sample objects against the derived schema before running the suite.
- Place the generated schema in an OpenAPI components.schemas block so API docs stay in sync with code types.
- Use the schema as a contract test at the service boundary — reject payloads that do not match the interface.
- Publish the schema alongside your npm package so downstream consumers get machine-readable validation.
- Feed the schema to a data pipeline validator to catch malformed records during ETL jobs.
- Paste an interface from a pull request, generate the schema, and attach it to the review as documentation.
- Convert both sides of a frontend/backend contract and diff the schemas to spot mismatches.
- Pair with the JSON Schema Validator: generate a schema here, then validate real payloads against it.
- Keep a generated schema in the repo and regenerate it whenever the interface changes as a drift check.
Common Mistakes and Troubleshooting
Pasting a whole .ts file with imports
The parser looks for interface and type declarations at the top level. import statements, function signatures, class bodies, and enum blocks are ignored rather than converted. If your file is large, copy only the interface or type declarations you need. Strip imports first — the parser treats import type { Foo } from './foo' as noise and skips it safely, but leftover runtime code can confuse field detection.
Conditional and mapped types are not expanded
Types like Partial<T>, Record<K, V> (handled only with two concrete arguments), template literal types, and conditional types (T extends U ? X : Y) require full type inference. The parser emits an empty schema for these and moves on. Inline the resolved shape manually, or convert the expanded type after your IDE's "Go to Type Definition" resolves it.
Cross-referenced types produce a definitions block
When two interfaces reference each other, the schema moves shared types under definitions and uses $ref. If your validator expects a flat schema with no $ref, copy the definition contents inline and replace each $ref manually. Alternatively, validate with a resolver that follows $ref — AJV and most backend validators do this by default.
Optional properties and required arrays
A property marked with ? is excluded from the required array but still appears under properties. This matches JSON Schema semantics: absence is allowed, but presence must match the declared type. If you want truly disallowed properties, add "additionalProperties": false after generation — the tool leaves it off by default so partial objects remain valid.
Smart quotes and curly braces from docs
Copying from a wiki or word processor can replace straight quotes with curly quotes or convert hyphens to en-dashes. The parser treats these as regular characters, producing field names like “name” instead of name. Paste from a code editor whenever possible, or run the input through a plain-text cleanup first.
number vs integer
TypeScript's number type covers both integers and floats, so the converter always emits "type": "number". If your schema must distinguish integers (for example, an ID field), add "type": "integer" manually or annotate with a JSDoc comment the backend validator understands. JSON Schema's integer type rejects 3.0 because it parses as a float.
Conversion Best Practices
- ✓ Convert one aggregate root at a time: generate the schema for the top-level interface, then convert related types separately and merge with $ref if needed.
- ✓ Annotate with JSDoc: add @minimum, @maxLength, @format, and @enum tags to interfaces so the generated schema carries real constraints rather than bare types.
- ✓ Regenerate on change: treat the schema as a build artifact. When the interface changes, rerun the converter and commit the updated schema in the same PR.
- ✓ Validate the output: paste the generated schema into the JSON Schema Validator with a sample payload to confirm it behaves as expected.
Integration and Security Tips
- ✓ Keep the schema local: conversion happens in your browser, so proprietary domain models and pre-release types are never transmitted.
- ✓ Pair with the reverse tool: if you have JSON data and need TypeScript, use the JSON to TypeScript converter for the opposite direction.
- ✓ Format before committing: run the result through the JSON Formatter to enforce 2-space indentation in the checked-in file.
- ✓ Document with descriptions: every description string in the schema doubles as API documentation for Swagger UI and Redoc.
Frequently Asked Questions
What TypeScript constructs does this converter understand? ▼
It handles interface declarations (including optional properties and extends notices), type aliases, string/number/boolean literal types, union and intersection types, array and tuple types, Record<string, T> mapped shapes, nested objects, and common primitive keywords like string, number, boolean, null, any, and unknown. JSDoc tags such as @minimum, @maximum, @minLength, @maxLength, @pattern, @format, and @enum are translated into their matching JSON Schema keywords.
Why does my interface with an extends clause produce a warning? ▼
The parser reads the extends clause and lists a warning so you know inherited properties are not automatically merged into the output. Full inheritance resolution requires a TypeScript compiler, which would add megabytes to a browser-only tool. If the parent interface is also pasted into the editor, both types are converted and cross-referenced with $ref, so you can merge them manually in one step.
What is the difference between $ref and inline schemas? ▼
When only one type exists, its schema is emitted inline with no $ref. When multiple interfaces are defined and they reference each other, the tool moves shared types into a definitions block and points at them with $ref following RFC 6901 JSON Pointer syntax. This avoids duplicating a type that appears in several places and matches how most backend validators resolve shared schemas.
How are union types converted? ▼
A union like string | number becomes anyOf with two branch schemas. A common special case is T | null — the null branch is folded into a single nullable schema by dropping the non-null type and letting most validators treat missing/null as acceptable. Literal unions like "admin" | "user" become an enum, which is the most compact representation in JSON Schema.
Does the tool run the TypeScript compiler? ▼
No. A full TypeScript compiler is several megabytes and needs Node or a worker bundle, which conflicts with the requirement to keep pages small and hydration-free. Instead the converter uses a hand-written parser that covers the most common declaration patterns. It is accurate for the majority of API models and config types, but may not handle conditional types, template literal types, or advanced mapped types.
Is my TypeScript code uploaded anywhere? ▼
No. Parsing and schema emission happen entirely in your browser. The tool makes no fetch calls with your source text. You can safely convert proprietary domain models, pre-release API interfaces, or internal config types without exposing them to third parties.
Can I use the output with OpenAPI or Swagger docs? ▼
Yes. The generated schema uses draft-07, which OpenAPI 3.0 supports natively through its schema object. Copy the schema into a components.schemas entry in your OpenAPI document, or use it with validators like AJV. For OpenAPI 3.1, which aligns with JSON Schema 2020-12, most draft-07 keywords carry over unchanged.
TypeScript and JSON Schema Conversion Tools
Derive TS interfaces from sample JSON — the reverse direction
Create a schema directly from JSON sample data
Validate payloads against your generated schema
Beautify the generated schema before committing
Compress TypeScript/JS output for bundles
Extract structured data from HTML markup