Skip to main content

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.

Your data never leaves the browserUpdated: July 2026

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" } produces v: 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 than any[].

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.

typescript
// 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

The real contract belongs in an OpenAPI schema or a runtime validator such as zod. TypeScript types are erased at compile time, so data arriving over the network that does not match your interface produces no warning at all. Treat generated types as a starting point and review them by hand.

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 sampleGenerated typeWhat it means
Present everywhere, a stringnote: stringRequired field
Missing from some recordsnote?: stringThe key may be absent
Sometimes explicitly nullnote: string | nullKey present, value empty
Empty arrayitems: unknown[]Element type unknowable
Different types per recordv: number | stringA genuine type conflict
How each situation in a JSON sample maps onto a TypeScript declaration.

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:00Z is a string on the wire, and the tool reports it as a string. Converting it to a Date is your job, and if you edit the type to say Date without adding the conversion the compiler will not catch you, because the result of JSON.parse is 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.