Find your next tool

Browse tools or search by task

Explore JSONToonPro

JSON Tools

JSON to TypeScript Interface Generator

Generate TypeScript interfaces and type definitions from JSON online. Infer nested object, array and primitive types from sample API responses.

Runs locally in your browser. Nothing you paste is uploaded.

JSON to TypeScript workspace
Root name
3 types
Input/ JSON
375 chars20 lines
Output/ TypeScript
307 chars22 lines

How to Generate TypeScript From JSON

Paste representative JSON and choose a root interface name. The generator infers a useful first draft of the data shape. Review the output against the API’s documented contract before adding it to a project.

How JSON Values Map to TypeScript Types

Strings, booleans and numbers map to their TypeScript counterparts. Objects become structured declarations and arrays need an element type. Runtime JSON parsing and TypeScript compile-time typing are separate concerns.

Nested Interfaces and Arrays

Nested objects can produce named interfaces so complex responses remain readable. Inspect arrays with different element shapes and resolve naming collisions. A short sample may omit variants seen in production.

Optional, Nullable and Repeated Fields

Null in a sample does not reveal the intended non-null type. A missing field can indicate an optional property, but a single record cannot prove optionality. Review unions and repeated shapes explicitly.

Using Generated Types With APIs

Generated interfaces help editors and static checks, but they do not validate a network response. Pair them with runtime validation where untrusted data enters your application and keep generated types aligned with API changes.

More examples and technical details

How Types Are Inferred From a Sample

Generating TypeScript from JSON is structural inference. The generator walks the value, and at every node it asks what shape this is: an object becomes an interface with one member per key, an array becomes an element type followed by square brackets, and a primitive becomes string, number, or boolean.

For an array of objects, the generator does not stop at the first element. It merges every element to build one type that describes all of them, which is what makes optional properties and unions possible. Nested objects become their own named interfaces rather than inline literals, because named types produce far more readable errors and can be imported individually.

Side by Side Example

A paginated API response and the interfaces it produces.

JSON response

{
"page": 2,
"total": 137,
"next_cursor": null,
"results": [
{
"id": "usr_81",
"email": "kai@example.com",
"verified": true,
"profile": { "display_name": "Kai",
"locale": "en-GB" }
},
{
"id": "usr_82",
"email": "sam@example.com",
"verified": false,
"profile": { "display_name": "Sam" },
"invited_by": "usr_81"
}
]
}

Generated TypeScript

export interface Profile {
display_name: string;
locale?: string;
}
 
export interface Result {
id: string;
email: string;
verified: boolean;
profile: Profile;
invited_by?: string;
}
 
export interface ApiResponse {
page: number;
total: number;
next_cursor: null;
results: Result[];
}

Two keys picked up a question mark because they were present in one result and absent in the other. And next_cursor was typed as null, which is almost certainly wrong: see the caveat section.

Interface or Type Alias

SituationPreferReason
Describing an object shapeinterfaceIdiomatic, and error messages name the interface
A public API surface others extendinterfaceDeclaration merging lets consumers augment it
A union of several shapestypeInterfaces cannot express a union
A primitive or tuple aliastypeInterfaces only describe object shapes
A mapped or conditional typetypeOnly type aliases support these operators
Anything recursiveEitherBoth handle self reference correctly

The practical difference is smaller than the debate around it suggests. Object shapes as interfaces and everything else as type aliases is a rule that never causes friction.

Optional Properties, Unions, and Null

The interesting inference happens when records disagree with each other.

  • A key present in some records and missing from others is marked optional with a question mark, which types it as its inferred type or undefined.
  • A key that holds different types across records produces a union, so a field seen as both a number and a string becomes string | number.
  • A key that is sometimes null and sometimes a value becomes a nullable union such as string | null. This is different from optional, and the difference is real: optional means the key may be absent, nullable means the key is present and empty.
  • An array with mixed element types produces an array of a union, written as (string | number)[]. An empty array in the sample gives the generator nothing to work with and produces unknown[] or any[].
  • An object with no keys at all becomes Record<string, unknown>, since there is nothing to describe.

Under strictNullChecks, which every project should have on, null and undefined are not assignable to other types. That is what makes the optional versus nullable distinction enforceable instead of decorative. A field typed string | null forces you to handle the empty case at the point of use, which is the entire value of the exercise.

The Caveat That Matters Most

Types generated from a sample describe that sample. They do not describe the API contract, and the difference between those two things is where the bugs live.

What the sample showedWhat was generatedWhat is actually true
next_cursor was nullnext_cursor: nullstring | null once there is a next page
Every user had a localelocale: stringOptional, absent for new accounts
status was always activestatus: stringA closed union: "active" | "suspended" | "closed"
tags was an empty arraytags: unknown[]string[]
No error object appearedNot present at allReturned on every failure path
id looked numericid: numberSent as a string by the API in some regions

Treat the generated file as a first draft that saves you the typing, then read the API documentation and correct it. Pay particular attention to fields that were null or empty in your sample, enumerable status fields that deserve a literal union, and every error shape, because error responses are almost never in the happy path sample you copied.

Types Vanish at Runtime

TypeScript types are erased during compilation. Nothing checks that the JSON arriving over the network actually matches the interface you wrote, and a response that has changed shape will flow straight into code that assumes otherwise.

A type assertion is a promise, not a check

// No validation happens here at all
const data = (await res.json()) as ApiResponse;
 
// A schema validates and infers the type from one source
const Result = z.object({
id: z.string(),
email: z.string().email(),
verified: z.boolean(),
});
type Result = z.infer<typeof Result>;
const data = Result.parse(await res.json());

Runtime validation libraries such as Zod, Valibot, io-ts, and ArkType let you declare the shape once and get both a runtime check and a static type from it. For anything crossing a trust boundary, which means network responses, form input, webhook payloads, and files on disk, that is worth the small amount of extra code.

Types are one way to pin down a payload's shape. Look at the full converter collection for JSON Schema when you need runtime validation rather than compile time types, JSON to GraphQL when the same shape needs an SDL definition, and JSON to SQL when it needs a table to live in.

Standards and references

Frequently asked questions

How do I generate a TypeScript interface from JSON?

Paste sample JSON, set the root name and copy the generated interface declarations.

How are arrays typed?

Array types are inferred from available sample values; review heterogeneous or empty arrays.

How are null values handled?

Null needs an explicit type decision or union because it does not reveal future values.

Can nested objects become separate interfaces?

Yes. Nested object shapes can become separate named declarations.

Is sample JSON uploaded?

No. Input is processed in this browser tab and is not uploaded by the tool. Copy and download are actions you control; ordinary page analytics do not receive tool input.