JSON to TypeScript Interface
Turn a JSON sample into TypeScript interfaces. Infers nested shapes, merges array element types, and marks fields optional when a sample omits them.
Paste a JSON sample on the left and the types appear here.
Key Takeaways
- Most converters read only the first array element; this one merges every element and marks a field missing from any of them as optional.
- Genuinely conflicting types become a union:
{ "v": 1 }plus{ "v": "1" }producesv: number | string. - A generated type describes your SAMPLE, not the API contract, and TypeScript types validate nothing at runtime.
- An empty array carries no element type, so the default output is
unknown[]rather thanany[].
The hand-written type that broke on the second record
Typing an API response by hand looks easy while you are staring at the first record. You transcribe the fields, the editor stops complaining, and the job feels finished. Then the second record in the list arrives without a note field and response.note.trim() throws at runtime. The type file still compiles, because what it describes is not the data, it is a photograph of one record.
Why merging across array elements matters
Most online converters see an array, take the first element, and discard the rest. If the first user in your sample has a profile picture, the type says avatar: string, and the compiler will never warn you about the user who does not have one, because the type is confidently wrong. This engine infers a shape for every element and merges them: a field present on one side and absent on another becomes optional, and a field that arrives with different types becomes a union.
// Input[ { "id": 1, "note": "first record", "score": 10 }, { "id": 2, "score": "10" }] // A first-element-wins converter (wrong)interface Item { id: number; note: string; score: number } // What this tool emitsexport type RootList = Root[]; export interface Root { id: number; note?: string; // absent in the second record score: number | string; // a real type conflict}Nested objects are emitted as their own named interfaces rather than inline literals. An anonymous object three levels deep is unreadable and cannot be referenced from anywhere else in your code. An object under a plural key such as users is named User, and a name that would collide gets a numeric suffix like User2.
A generated type is not a contract
This is the boundary worth internalizing: the output describes the sample you pasted. When you see note?: string, it does not mean the field is optional in the API, it means the field was missing from at least one record in your sample. Maybe it really is optional, or maybe your sample was too narrow. The reverse holds too: a field present in every record you happened to capture looks required even when the server omits it under conditions you did not exercise.
Note
Optional and nullable are different things
TypeScript distinguishes two situations that people routinely conflate, and getting them backwards puts your guard in the wrong place. note?: string says the key may be absent, so reading it gives you undefined. note: string | null says the key is always present but its value may be empty. A nullable database column and a key omitted from a JSON response are separate events. When your sample contains an explicit null, this tool produces a union; when the key is simply missing, it marks the field optional.
| Seen in the sample | Generated type | What it means |
|---|---|---|
| Present everywhere, a string | note: string | Required field |
| Missing from some records | note?: string | The key may be absent |
| Sometimes explicitly null | note: string | null | Key present, value empty |
| Empty array | items: unknown[] | Element type unknowable |
| Different types per record | v: number | string | A genuine type conflict |
Empty arrays and why unknown beats any
Given "tags": [], the engine has no information about the element type and emits unknown[] by default. The reason to prefer unknown over any is that any permits every operation, so from that point on the compiler stops helping and the mistake hides until runtime. unknown forces you to narrow the value before using it, which keeps the missing information visible in the code rather than buried. The options panel can switch this back to any, but leaving the default alone is the better habit.
Frequently Asked Questions
- Are these types safe to use in production?
- As a starting point, yes; as the final word, no. The output describes your sample, so review it against the API documentation and confirm that the optional fields really are optional. If you want an actual guarantee about data coming off the network, add a runtime validator such as zod alongside the type.
- Why is my field marked optional?
- Because at least one element of the array you pasted did not contain that key. That either means the field is genuinely optional or that your sample was too small to be representative. Paste more records and the picture sharpens.
- What about date fields?
- JSON has no date type. A value like
2026-07-26T10:00:00Zis a string on the wire, and the tool reports it as a string. Converting it to aDateis your job, and if you edit the type to sayDatewithout adding the conversion the compiler will not catch you, because the result ofJSON.parseis untyped to begin with. - Can I paste a very large sample?
- Samples of a few megabytes are handled without trouble, but size is rarely what you need. Variety matters more than volume for shape inference: fifteen or twenty records that between them exercise every optional field beat thousands of identical ones.
- Should I use an optional field or null?
- Use optional (
note?: string) when the field is sometimes not sent at all, and nullable (note: string | null) when it is always sent but may be empty. If you are designing the API, pick one convention and hold to it; mixing absent keys and null values in the same response forces every consumer to check for both.