Skip to content
Tools

Convert JSON to TypeScript Types (With Optional Fields Done Right)

· 5 min read

To convert JSON to TypeScript types, paste a sample into a generator such as the JSON to TypeScript tool, then check every field against more than one real response: a field is optional (name?: string) only if some objects omit it, and nullable (name: string | null) only if it can be null. Generating from an array of several objects lets the tool infer both automatically.

The basic mapping

JSON valueTypeScript typeNotes
"text"stringDates stay string; JSON has no date type
42, 3.14numberIntegers above 2^53 lose precision in JSON.parse
trueboolean
nullnullUsually combined: string | null
{ ... }interface or type aliasOne named type per nested object reads best
[ ... ]T[]Empty array gives unknown[] until you know more

Why a single sample gives wrong types

A generator only sees what you paste. Given one user object, every key looks required and every null looks like the only possible value. Take this response:

[
  { "id": 1, "name": "Ada", "email": "ada@example.com", "manager": null },
  { "id": 2, "name": "Linus", "manager": { "id": 1 } }
]

From the first object alone you would get email: string and manager: null, both wrong. Comparing both objects gives the real shape:

export interface User {
  id: number;
  name: string;
  email?: string;              // missing in some objects
  manager: Manager | null;     // present, but sometimes null
}

export interface Manager {
  id: number;
}

This is why the best input for type generation is an array of varied real records, not a single hand-picked one. The JSON to TypeScript tool merges the objects of an array: keys missing from some objects become optional, and fields seen as both a value and null become a union.

Optional vs nullable vs undefined

These three are easy to blur, but they mean different things to the code that consumes the type:

  • email?: string means the key may be absent. Reading it gives undefined.
  • email: string | null means the key is always present, and its value may be null.
  • email?: string | null means both can happen. Common in loosely specified APIs.
  • JSON itself cannot contain undefined. JSON.stringify drops keys whose value is undefined, which is why an optional property and a missing key round-trip the same way.

With exactOptionalPropertyTypes enabled in tsconfig.json, TypeScript also stops you assigning undefined explicitly to email?: string, which keeps "missing" and "present but undefined" apart. It is off by default and not part of strict.

Arrays, unions and edge cases

  • Mixed arrays. [1, "two"] becomes (number | string)[]. If the mix is accidental (an ID sometimes sent as a string) fix the API or normalize it rather than spreading the union through your code.
  • Empty arrays. [] tells a generator nothing. Expect unknown[] or any[] and replace it by hand once you know the item type.
  • Dictionaries. An object keyed by IDs, such as { "u_1": {...}, "u_2": {...} }, should become Record<string, User>, not an interface with a property per ID. Generators cannot always tell the difference, so check objects with many similar keys.
  • Enums. A status field with values "active" and "banned" is inferred as string. If the API documents a fixed set, narrow it by hand: status: "active" | "banned".
  • Big numbers. IDs like 9007199254740993 exceed Number.MAX_SAFE_INTEGER and are silently rounded by JSON.parse. Many APIs send them as strings for that reason; type them as string.

Interface or type alias?

For plain JSON shapes either works. Interfaces give slightly clearer error messages and can be extended or merged; type aliases are needed for unions such as type Id = string | number. Most generators emit interfaces for objects and aliases for unions, which is a sensible default.

Types are not validation

TypeScript types disappear at compile time. This compiles, and is still wrong at runtime if the API changes:

const res = await fetch("/api/users");
const users = (await res.json()) as User[]; // a promise, not a check

For data you do not control, validate it when it enters your program. A Zod schema gives you both the check and the type from one definition, and the JSON to Zod tool generates one from the same sample:

import { z } from "zod";

const Manager = z.object({ id: z.number() });

const User = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().optional(),
  manager: Manager.nullable(),
});

export type User = z.infer<typeof User>;

const users = z.array(User).parse(await res.json()); // throws on bad data

If other languages or services need the same contract, generate a JSON Schema instead with the JSON Schema generator and share that; see the JSON Schema beginner guide.

A practical workflow

  1. Collect several real responses, including edge cases: empty lists, missing optional data, error states.
  2. Combine them into one JSON array and check it parses in the JSON validator.
  3. Generate types with the JSON to TypeScript tool and give the root type a meaningful name.
  4. Review by hand: narrow enums, turn ID-keyed objects into Record, type big IDs as string, replace unknown[].
  5. Add runtime validation (Zod or a JSON Schema validator) at the boundary where the JSON arrives.

API responses often contain tokens or personal data. The generator runs in your browser and nothing is uploaded, but if you paste samples into a ticket or chat, scan them with the secret scanner first.

FAQ

How do I convert JSON to a TypeScript interface?

Paste the JSON into a generator such as JSON to TypeScript, name the root type, and copy the result. Use an array of several real objects so optional and nullable fields are inferred correctly.

How do I make a field optional in TypeScript?

Add a question mark after the property name: email?: string. Use it when the key can be missing from the JSON, and string | null when the key is present but can be null.

Does TypeScript validate JSON at runtime?

No. Types are erased when compiling, so as User is only an assertion. Use a runtime validator such as Zod or a JSON Schema validator for untrusted data.

Should I use interface or type for JSON data?

Either works for object shapes. Interfaces are a common default for objects; type aliases are required for unions and mapped types.

How are JSON dates typed in TypeScript?

As string, because JSON has no date type. Convert with new Date(value) after parsing, or use a Zod transform if you want Date objects.

More JSON guides