Skip to content
Free Tool Arena

Developers & Technical · Guide · Developer Utilities

How to generate TypeScript from JSON

How generators infer types, optional vs required, enums vs strings, quicktype/openapi-typescript/graphql-codegen, runtime validation pairing.

Updated April 2026 · 6 min read

Hand-writing TypeScript interfaces for API responses is tedious and error-prone. Generating them from sample JSON (or a JSON Schema, OpenAPI spec, or protobuf) is faster, more accurate, and stays in sync with the data. But generators make choices — optional vs required, union vs enum, narrow vs loose types — that affect how pleasant the generated types are to use. This guide covers the generators worth knowing, the tradeoffs in their inference, when to run-time validate in addition to compile-time check, and the patterns for keeping generated types fresh as APIs evolve.

Advertisement

What a generator does

Given sample JSON:

{
  "id": 42,
  "name": "Alice",
  "active": true,
  "tags": ["admin", "staff"]
}

A generator produces:

interface User {
  id: number;
  name: string;
  active: boolean;
  tags: string[];
}

Straightforward for flat data. Complications start with nested objects, mixed arrays, optional fields, and null handling.

Inference decisions you’ll hit

Optional vs required: does the generator assume all fields in the sample are required? Or only mark required keys (if multiple samples are provided)? You usually want to treat single-sample inference as a best-effort.

Nullable fields: if a sample has "email": null, is it email: null, email?: string, or email: string | null? Depends on the generator.

Number vs literal: "status": 1 — is it status: number or status: 1? Literal types are more precise but brittle if the API returns other numbers.

Array item union: ["a", 1, true] becomes (string | number | boolean)[]. Usually fine, but sometimes the generator gets cold feet and outputs any[].

Enum vs string: "status": "pending" status: string or status: "pending"? Single-sample enums are dangerous; multi-sample inference can detect real enums.

Generators worth knowing

quicktype: old, reliable, supports many source formats (JSON, JSON Schema, GraphQL, Postman) and many target languages. Output is ergonomic.

json-schema-to-typescript: converts JSON Schema (not raw JSON). Output is faithful to the schema; requires you have a schema already.

openapi-typescript / openapi-fetch: generate types and typed fetch clients from OpenAPI. Best for REST APIs.

graphql-codegen: for GraphQL APIs, generates types plus hooks for Apollo/urql/others.

protobufjs / ts-proto: for gRPC/protobuf.

VS Code paste-as-JSON: built-in command turns pasted JSON into a TypeScript interface. Good for one-offs.

From JSON alone — no schema

Fine for quick typing, but:

Only one sample → generator can’t distinguish optional from required.

No way to know if an array element is always an object or sometimes null.

No range/length/pattern info.

Hack for better inference: feed the generator multiple samples covering edge cases (empty string, null, missing keys). It’ll infer unions and optionals more accurately.

From JSON Schema — richer types

JSON Schema carries constraints JSON doesn’t. A schema with:

"status": { "enum": ["draft","published","archived"] }

Generates:

status: "draft" | "published" | "archived";

Not possible from raw JSON. Worth writing a schema for anything mission-critical.

Runtime validation matters too

Generated TypeScript interfaces are compile-time only. If an API returns malformed data, your code trusts it and crashes later.

Pair generated types with runtime validation:

Zod: define schema once, get both TS type and runtime validator.

ajv: compile JSON Schema to a fast runtime validator.

io-ts / runtypes: similar pattern in the fp camp.

At the API boundary: parse untrusted JSON through a validator, trust the types from there on.

Naming and style

PascalCase for types: User, not user.

Interface vs type alias: interface for object shapes, type for unions and mapped types. Most generators output interfaces.

Nested type names: some generators inline nested objects; others create named sub-types like UserAddress. Named sub-types are usually better for reuse.

camelCase vs snake_case: if your API returns snake_case, decide whether to keep it in TS or transform. Keeping it matches the API wire format; transforming is nicer ergonomically but requires mapping at the boundary.

Keeping types fresh

APIs change. Generated types should regenerate automatically.

CI step: regenerate types on every build/deploy. Fail if the schema changed but types weren’t regenerated.

Watch mode in dev: regenerate on schema save. Tight feedback loop.

Commit generated files: yes. Keeps diffs visible in PRs. Regenerate in CI to detect drift.

Common mistakes

Trusting single-sample inference. Missing fields get inferred as non-optional. Add multiple samples or annotate manually.

No runtime validation at API boundaries. Generated types lie when data is malformed. Validate untrusted input.

Regenerating without reviewing the diff. A schema tweak can change every interface. Review changes like any code.

Mixing hand-written and generated in the same file. Regen overwrites your edits. Keep generated output separate.

Ignoring null/undefined distinctions. TypeScript treats them as different. Generated types should too.

Leaving any leak in. Strict mode + noImplicitAny catches this. Review generator output for stray any.

Run the numbers

Convert JSON to TypeScript interfaces instantly with the JSON to TypeScript converter. Pair with the JSON schema generator to get a reusable schema, and the JSON formatter to clean up the sample first.

Advertisement

Found this useful?Email