JSON to TypeScript, Go, Zod & Python Schema
Turn a JSON sample into TypeScript interfaces, or switch the output to Go structs with json tags, Zod schemas, or Pydantic models. Name the root type and the nested ones follow it.
TypeScript
The types appear here.
Read from every row, with the optional fields marked.
That isn't valid JSON.
One interface per shape
Output
What the merge did
Start here
- Put the JSON in the JSON well. A response body copied straight out of the network tab is ideal; paste a list with several rows if you have one, because every row is read.
- Replace Root in Root type name with something meaningful. Every nested type is built off that name, so User beats Root by a mile once the output lands in a file alongside other types.
- Choose TypeScript, Go, Zod or Python on the Output track, then press Copy output, or Download for a file named after the root.
About this tool
Paste a real response on the left, put a name in Root type name, and the TypeScript interfaces for it appear on the right. JSON to TypeScript is what most people arrive for, and the loop is quick enough to beat typing the interface out by hand even for a small object. Output regenerates on every keystroke: trim a field out of the sample and the types follow within the same second.
Every row of an array is read before anything is written. A key that turns up in some rows and not others comes back optional, a key that holds a number in one row and a string in the next comes back as number | string, and a key that is null everywhere widens to unknown | null rather than pretending null is a type. Each shape is emitted once, so two rows never give you two copies of the same interface, and the strip under the panes counts what the merge did.
Names are built from the root rather than invented. A nested object takes the root name plus its own key capitalised, so a User with a profile object gives you UserProfile, and an array drops the trailing s first, which is why an items array of objects gives you UserItem. When two different shapes would land on the same name, the second takes a number. The Output track switches the target: Go structs with `json:"…"` tags and pointers for fields that can be missing, Zod schemas in dependency order, or Pydantic models with a None default where a field can be absent, all from the same merged reading.
Good to know
- Types mirror the sample. A field that never appears in your paste is missing from the output too, and a field that is optional in real life but present in every row you pasted comes back required.
- A top-level array wants a plural root name. Call it Users and you get interface User plus type Users = User[]. Call it User and the element takes User2 so the two names never collide.
- Integers and decimals are told apart by the sample: a price that happens to be exactly 10 types as int in Go and Python.
Frequently asked questions
Can I paste a JSON Schema instead of an example?
It reads concrete values only. Hand it an object or an array with real data inside and the shape comes straight off that. A JSON Schema document goes in as ordinary JSON, so what you get back is a type describing the schema file itself, which is almost never the thing you wanted.
Do I get exports and a z.infer type with the Zod output?
Neither of those, no. The schemas come out as plain consts in dependency order, with import { z } from "zod" on the first line, so a nested schema is always defined before the object referencing it. Add export where you need it, and type User = z.infer<typeof User> if you want the static type alongside the runtime check.
How does it decide between int and float?
By whether the number in your sample has a fractional part. 42 becomes int in Go and int in Python, 9.5 becomes float64 and float. So a price that happens to be exactly 10 in the response you grabbed will type as an integer, which is the one place a single sample quietly misleads you. TypeScript and Zod have a single number type and sidestep the question.
What happens to a field that came back null?
When a field is null in some rows and a string in others, it comes back as string | null. When it is null in every row, TypeScript widens it to unknown | null, Go to interface{}, Zod to z.unknown().nullable() and Pydantic to Any | None, because a sample of nulls says nothing narrower. That is the strongest possible hint to go and fetch a second response.
Missing something in JSON to TypeScript, Go, Zod & Python Schema? Suggest a feature →
Tell someone who needs this.