JSON to TypeScript Converter

JSON to TypeScript Converter

Generate TypeScript interfaces from JSON data. Support for nested objects, arrays, optional properties. Free online JSON to TS interface generator

Hand-writing TypeScript interfaces from sample API responses is busywork — and the typical mistake is too narrow: typing a field as string when the API sometimes returns null, or as number when it is sometimes 0 (a falsy default). This generator infers types from one or more JSON samples, merges them so a field present in some samples and absent in others becomes optional, narrows numeric literals to number, and produces interfaces or types you can paste into your codebase.

How inference actually works

  • Primitive types map directly — string, number, boolean, null. JavaScript has no integer type; a value that looks like 42 is typed number, not integer.
  • Arrays infer from element types. [1, 2, 3] → number[]. Mixed arrays produce unions: [1, "x", null] → (number | string | null)[]. Empty arrays default to unknown[] — narrow manually if you know better.
  • Objects infer from their keys recursively. Nested objects become inline types or extracted interfaces (configurable).
  • Optional vs nullable — different concepts. A field that may be absent gets ?; a field that may be null gets | null. Both at once is | null and ? — the conservative default for fields not present in every sample.
  • Type names — derived from the JSON property they live on, PascalCased. user_profile → UserProfile, list of orders → Order (singularized — heuristic, sometimes wrong).

Working example: merging two samples

Input

Sample 1:
{ "id": 1, "name": "Ada", "email": "ada@example.com" }

Sample 2:
{ "id": 2, "name": "Linus", "email": null, "verified": true }

Output

export interface Root {
  id: number;
  name: string;
  email: string | null;
  verified?: boolean;
}

Notes:
  email — string in sample 1, null in sample 2 → string | null
  verified — present only in sample 2 → optional (boolean | undefined)

Without sample 2, email would be typed string and your code would crash on the first null. Always feed at least 3-5 representative samples covering edge cases (empty arrays, missing optional fields, null values).

Configuration choices that matter

  • interface vs type — interfaces can be extended and merged; type aliases cannot. For API response shapes type is usually fine. For library-author definitions, interface is more flexible.
  • Inline vs extracted nested types — { user: { id: number; name: string } } vs interface User { id: number; name: string }. Extract when the type is reused or non-trivially deep; inline when it is one-off.
  • readonly fields — TypeScript supports readonly id: number which prevents reassignment. Useful for IDs and timestamps that should never change after fetch. Configurable; off by default.
  • Date inference — JSON has no date type. Strings matching ISO 8601 can be inferred as Date or kept as string. The Date inference is convenient for code but loses information (the API returns a string; calling .getTime() on it crashes). Keep as string by default; convert with new Date(...) at the boundary.
  • Enum vs union of string literals — type Status = "pending" | "active" | "closed". Enums (enum Status {...}) carry runtime weight; unions are erased. Prefer string-literal unions unless you need runtime iteration.

When to reach for this tool

  • You are integrating an undocumented API and need TypeScript types fast. Hit the endpoint a few times, paste the responses, get types you can refine.
  • You inherited a JavaScript codebase being migrated to TypeScript and need rough types for response shapes scattered across thirty fetch calls.
  • You are writing API tests and want strongly-typed expected-response builders without hand-writing forty fields.
  • You are debugging "the API used to return X but now returns Y" — feed both responses, see the type diff, identify which fields changed.

What this tool will not do

  • It will not understand the semantics of your data. A field with values "y" and "n" gets typed as string, not as a boolean-with-conversion-required. A timestamp as Unix seconds vs Unix milliseconds both type as number; only your reader knows the difference.
  • It will not infer discriminated unions automatically. If your API returns { type: "circle", radius: 5 } or { type: "square", side: 4 }, you get a single merged type with all fields optional. Hand-edit to define a proper discriminated union — the inference cannot prove the constraint.
  • It will not validate at runtime. Generated types are compile-time only. If you actually need to ensure the API returned the shape you expected, generate Zod / io-ts / Valibot schemas instead.

Inference runs entirely client-side. Real production API responses (including those with user PII) stay in your browser.

Frequently asked questions

Why is my field typed as any?

Almost always one of: (1) input value was undefined or missing in all samples, (2) value was an empty array [] (cannot infer element type), (3) value was an empty object {} (no fields to infer). Provide a sample with actual data.

Should I use interfaces or types in TypeScript?

For external data shapes (API responses), either is fine — type is slightly more flexible (union types, intersections). For your own domain models you want to extend, interface is better. The TypeScript handbook officially treats them as interchangeable for most cases.

How do I handle a field that is sometimes an array and sometimes a single value?

The generator will produce a union: items: Item | Item[]. Best practice is to normalize at the boundary — turn the single value into a one-element array immediately on receipt — so the rest of your code only deals with the array case.

Why are numbers always typed as number, not int or float?

JavaScript has one numeric type (double-precision float). TypeScript inherits this. There is no int or float distinction. If you need integer constraints, use branded types or runtime validation.

Can I generate types from a JSON Schema instead of a JSON sample?

Yes — but use a different tool (json-schema-to-typescript) for that. Sample-based inference and schema-based generation have different trade-offs. Schema generation gives you constraints (min/max, regex patterns) that you cannot get from samples.

How do I make the generated types more strict?

After generation: change number to a literal union if you know the set of values, change string to "active" | "inactive", add readonly to immutable fields. The generator is a starting point — refine manually based on domain knowledge.

Related tools

Last updated · E-Utils editorial team