JSON to TypeScript
Paste JSON, get TypeScript types (and a Zod schema) that fit every sample.
One JSON document, or several samples one after another or one per line (JSON Lines). Comments and trailing commas are ignored. Your data stays in this browser.
Type inference
About the JSON to TypeScript
Paste a JSON response and get TypeScript types that describe it: nested objects become their own interfaces, arrays get an item type, and a property that is missing from some samples becomes optional (?) while one that is sometimes null becomes null | …. Paste several samples one after another, or one per line, and the types cover all of them — or start from a JSON Schema instead.
Choose the house style (interface or type, readonly, string-literal unions or enum, 2 or 4 spaces), and add a Convert class that checks JSON.parse results at runtime or a matching Zod schema. The generator is quicktype, running in your browser: private API responses never leave your device.
How to use it
- Paste JSON or open or drop a .json file, or press Load sample. For better types, paste several responses one after another (JSON Lines works too) — the result updates as you type.
- Set the Root type name, for example
Order. Nested types are named after their property, in the singular for arrays (items→Item). - Pick interfaces or type aliases, readonly properties, unions or enums, and whether you also want the runtime converter or a Zod schema.
- Read the notes under the code: they name fields that were always empty or always
nullin your samples, which need a better sample to get a real type. - Copy the code or download the
.tsfile (and the.schema.tsfile for Zod).
Examples
{"id": 1, "name": "Ada", "email": null}
{"id": 2, "name": "Bob", "email": "[email protected]", "nickname": "bobby"}export interface User {
id: number;
name: string;
email: null | string;
nickname?: string;
}Root type name User. nickname is missing from the first sample, so it is optional; email is null in one of them.
[{"role": "admin", "id": 0}, {"role": "editor", "id": 1}, … 12 users with three roles]export interface Users {
role: Role;
id: number;
}
export type Role = "admin" | "editor" | "viewer";With enum chosen instead, Role becomes export enum Role { Admin = "admin", … }.
{"id": 1, "tags": ["a"], "createdAt": "2026-09-28T14:05:31Z"}import * as z from "zod";
export const RootSchema = z.object({
id: z.number(),
tags: z.array(z.string()),
createdAt: z.coerce.date(),
});
export type Root = z.infer<typeof RootSchema>;With Date strings → Date on. Use RootSchema.parse(data) to check data from an API at runtime.
Common uses
- Typing a REST or GraphQL response before writing the fetch code.
- Turning a JSON Schema from an API spec into types and a validator for the client.
- Checking untrusted JSON at runtime with Zod or the generated converter instead of trusting
as. - Getting types for config files, webhooks payloads and test fixtures.
How the types are worked out
- Optional and nullable: a property missing from some samples gets
?; a property that isnullin some samples getsnull | T. All properties optional puts?on everything. - Arrays: the item type comes from every item; mixed items give a union such as
Array<number | string>. An array that is empty in every sample becomesunknown[](the notes say which). - Unions and enums: a string property becomes a union or enum only when it has at least 10 values in your samples and fewer distinct values than the square root of that count — 3 distinct roles across 12 users qualify, 2 samples never do. Turn off Detect enums to keep
string. - Maps: an object whose keys look like data (IDs, dates, many keys of the same type) becomes
{ [key: string]: T }. - Similar types: objects with mostly the same properties are merged into one type; turn off Merge similar types to keep them apart.
- Numbers: TypeScript has one
numbertype. Integers above 2^53 − 1 are rounded byJSON.parse, and the notes warn when your samples contain one.
Runtime converter or Zod?
TypeScript types disappear at runtime, and JSON.parse returns any, so nothing checks that the data really has the shape you declared. Both options add that check:
- Runtime converter: a
Convertclass withConvert.toOrder(json)andConvert.orderToJson(value). It throwsInvalid value for key "id" on Order. Expected number but got "1"when the data does not match, and by default also when an object has properties the types do not list — tick accept unknown properties to let them through. With Date strings → Date, date-time strings are turned intoDateobjects. - Zod schema:
OrderSchema.parse(data)with the Zod library; recursive types usez.lazy. The output for the sample data — types, converter and Zod schema — compiles in TypeScript strict mode against Zod 4.
Without either, date strings stay string, because that is what JSON.parse gives you.
From a JSON Schema
Switch the input to JSON Schema: required decides optional properties, enum becomes a union, type: ["string", "null"] becomes null | string, additionalProperties with a schema becomes a map, definitions under $defs or definitions keep their names, and an allOf that extends a type ({"$ref": "#/$defs/Pet"} plus more properties) becomes one interface with all of them. References must point inside the document (#/$defs/LineItem); the tool never downloads other schema files. JSON Schema allows extra properties unless additionalProperties: false says otherwise — tick Allow extra properties to get a [property: string]: unknown index signature for that. Validation-only keywords such as minimum, pattern or format have no TypeScript equivalent and are not carried over.
Limitations
- Types are only as good as the samples: a property seen once with a string is typed
stringeven if the API can also send a number. Paste several real responses. - Property names are kept exactly as in the JSON (quoted when needed, such as
"first-name"); the types are not renamed to camelCase. - JSON Schema keywords for conditions (
if/then/else,dependentSchemas,not) are ignored. - Very large inputs (several megabytes) are generated when you press Generate rather than as you type; generation stops after 30 seconds.
Privacy
Everything happens in your browser. What you enter or open here is not uploaded or stored by MySmartCoPilot. The code generator (about 330 KB compressed) is downloaded from this site once, like the rest of the page; your JSON is never uploaded.
Frequently asked questions
Is my JSON uploaded anywhere?
No. The generator runs in your browser (in a background worker), and the page works offline once it has loaded. Nothing you paste or open is sent to a server.
Why is a property optional when it is in my JSON?
Because it is missing from at least one sample — including one item of an array. If it should always be there, check the sample; if you want every property optional, tick All properties optional.
Should I use interface or type?
For object shapes they behave the same in almost every case. Interfaces can be extended and merged by declaration; type aliases can also name unions. Pick whatever your codebase already uses — the option only changes export interface X {…} into export type X = {…};.
Why did I get "string" instead of a union of values?
A union or enum is only inferred when a property has at least 10 values in your samples and only a few distinct ones (fewer than the square root of the count), so that a free-text field is not mistaken for a fixed set. Paste more samples, or edit the type by hand.
Which Zod version does the schema need?
It uses import * as z from "zod" and common parts of the API (z.object, z.array, z.union, z.enum, z.record, z.lazy, z.coerce.date). The output for the sample data was type-checked against Zod 4.