Skip to content

Formatters & Code

JSON to TypeScript Interface

Infer types from a JSON sample and emit TS interfaces.

Runs in your browser
JSON · source
lines: 17chars: 261size: 261 B
TypeScript · result
lines: 16chars: 248size: 248 B
live

Understanding JSON → TypeScript

A sample is a guess, not a contract.

What a JSON-to-TS inferrer is really doing, and the choices it makes that change the types you end up with.

Inference is induction.

Every JSON-to-TS converter is doing the same thing: read one or more sample documents, induce a structural type that's consistent with what it saw. It's pattern-matching, not proof. A field absent from the sample is assumed never to exist; a field that only appears as a string is assumed always to be a string. The inferred type is as accurate as the sample is representative.

type = ⋃ shapes observed in samples

Optional vs missing vs null.

JSON allows three things that TypeScript treats as different: a key that's absent entirely, a key whose value is the literal null, and a key whose value is the string "null". A good inferrer distinguishes "always present" → required field; "sometimes absent across samples" → field?: T; "sometimes literal null" → field: T | null. Many simple inferrers collapse the second and third into the same thing, which is wrong: a server that returns {} and one that returns {"x": null} behave the same to the runtime but differently to strictNullChecks.

Arrays of unions.

When an array contains items of different shapes — a list of events of different types, a heterogeneous result page — the right inference is a discriminated union. Find the field that varies between shapes and consistently identifies which is which (often type, kind or __typename); emit { type: "A"; ... } | { type: "B"; ... }. Naive inferrers will merge all the keys into one type with every field optional, losing the discrimination.

A worked inference.

Sample: { "id": 7, "title": "Hi", "tags": ["a","b"], "author": null }. A reasonable inferrer emits { id: number; title: string; tags: string[]; author: null }. If a second sample shows "author": {"name": "Q"}, the inferrer widens to author: null | { name: string }. If a third sample omits tags entirely, it widens to tags?: string[].

Single sample

One document, all fields treated as required

Most inferrers default to required because that's what they observed.

{ id:7, title:"Hi", tags:["a"], author:null }

= { id: number; title: string; tags: string[]; author: null }

After two samples

One has tags, one doesn't

Field absent in any sample becomes optional.

merge widens tags into optional

= { id: number; title: string; tags?: string[]; author: null | ... }

Numbers, dates, and the literal-vs-widened question.

A field with sample value 42 could be inferred as number (widely useful) or as the literal type 42 (rarely useful, occasionally correct for enum-like values). A field like "2026-05-13T12:00:00Z" is syntactically a string but conventionally a date; some inferrers offer to emit Date or a branded type. Tightening is a runtime cost: if the inferrer emits Date, your code has to parse the field on every JSON decode. Inferrers that default to string with a comment are usually right.

Snake_case vs camelCase.

Most JSON APIs use snake_case key names; most TypeScript code uses camelCase. The inferrer has to decide between three options: keep the names as they came in (everything quoted, lints complain), rename to camelCase (clean types but the JSON parse step needs a mapper), or emit both via a transform. Pick a single convention across the project and configure the inferrer accordingly. Don't mix: half-snake half- camel types are a source of constant bugs.

What the converter isn't.

The output is a starting point. It cannot tell you that id: number is actually a primary key, that email: string should be validated, that status: string is really one of seven enum values, or that the API will one day add a field. Review the inferred types and tighten them where domain knowledge allows: literal unions instead of bare strings, branded types for IDs, tuples instead of arrays when the length is fixed. The inferrer cannot know any of those things; you can.

Frequently asked questions

Quick answers.

How does the tool handle nested objects?

The converter recursively traverses the JSON structure and creates separate, named interfaces for nested objects to ensure the code remains modular and readable.

What happens with null values or empty arrays?

When a value is `null`, the tool typically types it as `any` or `unknown` because the intended type cannot be inferred. For empty arrays, it defaults to `any[]` unless further context is provided.

Can I customise the root interface name?

Yes. You can specify a custom name for the top-level interface to match your project's naming conventions, and the tool will update all references accordingly.

Is my data privacy protected?

Yes. The parsing and interface generation logic runs locally in your browser session. No data is sent to our servers or stored externally.

People also search for

Related tools

More in this room.

See all in Formatters & Code