{ } Schemato

Guide

How to convert JSON to TypeScript types

A practical walkthrough for turning a real JSON response into TypeScript interfaces, then deciding whether plain types are enough or runtime validation should come next.

Need it now? JSON → TypeScript converter.


Step 1 — Start with a representative JSON sample

Use a real API response, webhook payload, or fixture. Include nested objects and at least one item in each array so the generated type has enough information to work with.

{
  "id": 42,
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "isAdmin": false,
  "profile": {
    "company": "Analytical Engine Labs",
    "timezone": "Europe/London"
  },
  "tags": ["math", "engine"]
}

Step 2 — Generate TypeScript interfaces

Paste the sample into the JSON → TypeScript converter. The first pass gives you clean structural types:

export interface Root {
  id: number;
  name: string;
  email?: string;
  isAdmin: boolean;
  profile: Profile;
  tags: string[];
}

export interface Profile {
  company: string;
  timezone: string;
}

Step 3 — Rename and tighten the output

Generated names are a starting point. Rename the root type, review nested types, and check optional fields against your real API contract.

export interface User {
  id: number;
  name: string;
  email?: string;
  isAdmin: boolean;
  profile: UserProfile;
  tags: string[];
}

export interface UserProfile {
  company: string;
  timezone: string;
}

Step 4 — Use the type at your API boundary

TypeScript helps your editor and compiler, but it does not validate JSON at runtime. This pattern is fine when you trust the upstream response:

export async function fetchUser(): Promise<User> {
  const res = await fetch("/api/me");
  return (await res.json()) as User;
}

Step 5 — Upgrade to Zod when runtime validation matters

If the JSON comes from a third-party API, user form, webhook, env var, or localStorage, TypeScript alone cannot protect you. Generate a Zod schema instead and derive the type from that schema:

import { z } from "zod";

export const User = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email().optional(),
  isAdmin: z.boolean(),
  profile: z.object({
    company: z.string(),
    timezone: z.string(),
  }),
  tags: z.array(z.string()),
});

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

Plain TypeScript or Zod?

  • • Use TypeScript when the data is internal, already typed, or only used for editor support.
  • • Use Zod when data crosses a trust boundary and can be malformed at runtime.
  • • Use JSON Schema or OpenAPI input when you have a formal contract instead of just one sample.

Common pitfalls

Troubleshooting generated TypeScript types

TypeScript types generated from JSON are only as complete as the sample. Before using them as an API contract, check the cases that a single payload cannot prove.

Optional, nullable, or both?

A missing field and a field set to null mean different things in TypeScript. Compare the generated type against API docs, production fixtures, or more than one response sample.

// If this field may be omitted:
email?: string;

// If the API sends null explicitly:
email: string | null;

// If both can happen:
email?: string | null;

Empty arrays need real examples

If an array is empty in the sample, the converter cannot infer the element shape. Paste a payload with at least one item, or tighten the generated type by hand.

// Empty sample arrays cannot reveal the element type
const sample = { tags: [] };

// Weak first pass
interface User {
  tags: unknown[];
}

// Tighten it after checking real data
interface User {
  tags: string[];
}

Dates are strings over the wire

JSON has no native date type. Keep timestamps as strings in boundary types, then parse them where your app needs date operations.

interface Invoice {
  id: string;

  // JSON sends this as a string, even when it represents a date
  issuedAt: string;
}

const issuedAt = new Date(invoice.issuedAt);

Type assertions are not validation

as User can make the compiler quiet, but it cannot catch a broken API response. Use it only when you trust the source; otherwise upgrade the boundary to Zod.

// This only tells TypeScript to trust you.
// It does not validate the response at runtime.
const user = (await res.json()) as User;

FAQ

Should I use TypeScript interfaces or Zod?

Use TypeScript for trusted internal data. Use Zod when you need runtime validation for external input.

Can one JSON sample identify every optional field?

No. Review API docs or paste multiple representative samples into your own checklist before finalizing optional fields.

What about JSON Schema or OpenAPI?

If you have a formal contract, use JSON Schema → TypeScript or OpenAPI → TypeScript instead of inferring from a single sample.

Does as User validate JSON at runtime?

No. It only tells TypeScript to trust the value. If the JSON comes from an API, form, webhook, localStorage, or env var, validate it with Zod or another runtime schema.

Should JSON dates become Date or string?

Use string for the boundary type. Parse into Date only after the JSON has entered your application code.

Related