不喜欢广告? 无广告 今天

JSON to TypeScript 从 API 响应自动生成接口

更新于

为每个API响应手动编写TypeScript接口既繁琐又容易出错。学习如何从真实的JSON数据自动生成准确的接口,然后使用Zod添加运行时验证——因为类型在运行时会消失,而`any`并不是解决方案。

JSON to TypeScript: Auto-Generate Interfaces From API Responses 1
广告 移除?

You’ve just fetched data from a third-party API. The response is a dense JSON blob — nested objects, arrays, nullable fields — and now you have to figure out how to type it in TypeScript. So you open a new file, start pecking out interface User { ... }, and 20 minutes later you’ve got something that probably matches the actual data. Probably.

There’s a better way. Tools that convert JSON directly to TypeScript interfaces take that 20-minute chore down to seconds. This article walks through what those generated types look like, how to handle the awkward cases (nulls, unions, deep nesting), and why you should pair generated types with Zod schemas to catch shape mismatches at runtime — not just at compile time.

Why TypeScript Interfaces for API Responses Matter

TypeScript’s value proposition is catching errors before your code runs. Without typed API responses, you’re flying blind: accessing properties that might not exist, treating optional values as required, or silently converting a string "null" into something unexpected downstream.

Consider this common scenario:

const user = await fetchUser(id);
console.log(user.address.city); // TypeError at runtime if address is null

If you’d typed the response properly — with address: Address | null — TypeScript would have flagged that access immediately. The compiler is your first line of defense, but only if you give it something to work with.

Manually writing interfaces for every API is tedious and error-prone. You misread the schema, miss an optional field, or copy-paste a stale version. Generating interfaces directly from real JSON data removes that human error from the equation.

What the JSON-to-TypeScript Conversion Produces

Take a straightforward API response:

{
  "id": 42,
  "username": "jsmith",
  "email": "j@example.com",
  "createdAt": "2024-01-15T10:30:00Z",
  "role": "admin",
  "profile": {
    "bio": "Developer",
    "avatar": null
  }
}

Paste that into the JSON 转 TypeScript 接口生成器 and you get:

interface Profile {
  bio: string;
  avatar: null;
}

interface RootObject {
  id: number;
  username: string;
  email: string;
  createdAt: string;
  role: string;
  profile: Profile;
}

A few things to note:

  • Nested objects become their own interfacesProfile is extracted automatically rather than inlined.
  • Dates are typed as string — JSON has no date type, so ISO strings stay as strings. You’ll need to parse them yourself.
  • avatar: null is typed as literal null — which is accurate but incomplete. More on that below.

Handling Tricky Cases

Nullable Fields

When a field is null in your sample JSON, the generator types it as null. But in practice, that field probably switches between a real value and null depending on the data. You’ll want to adjust those manually:

// Generated
avatar: null;

// What you actually want
avatar: string | null;

The same goes for optional fields that happen to be populated in your sample — add ? to any property that may be absent in some responses.

数组

Arrays of objects are handled cleanly. Given:

{
  "posts": [
    { "id": 1, "title": "Hello", "published": true },
    { "id": 2, "title": "World", "published": false }
  ]
}

The generator produces:

interface Post {
  id: number;
  title: string;
  published: boolean;
}

interface RootObject {
  posts: Post[];
}

联合类型

If your JSON samples show a field holding different types across different records — say, a value that can be a number or a string — you should represent that as a union. Generated types won’t catch this from a single sample, so it’s worth checking against your API documentation:

value: string | number;

Deeply Nested Objects

Deep nesting is where manual typing really breaks down — and where generators earn their keep. A response with three or four levels of nesting gets decomposed into a clean hierarchy of named interfaces, each responsible for its own shape.

The Gap Between Compile-Time Types and Runtime Reality

Here’s the thing TypeScript newcomers often miss: types disappear at runtime. TypeScript compiles to JavaScript, and JavaScript has no concept of interfaces. If your API returns a shape that doesn’t match your declared type, TypeScript won’t know — and won’t tell you.

This makes the common pattern of casting API responses genuinely dangerous:

const data = await response.json() as User; // No validation, just trust

That cast tells TypeScript “trust me, this is a User” — but TypeScript has no way to verify it. If the API changes its shape or returns an error object instead, your code breaks at runtime in ways the compiler never warned you about.

The solution is runtime validation.

Zod for Runtime Validation

Zod is a TypeScript-first schema validation library. You define a schema once, use it to parse incoming data, and get back a fully-typed value — or a detailed error if the shape doesn’t match. No casting, no guessing.

Using the same JSON sample from before, the JSON 到 Zod Schema 生成器 produces:

import { z } from "zod";

const ProfileSchema = z.object({
  bio: z.string(),
  avatar: z.null(),
});

const RootObjectSchema = z.object({
  id: z.number(),
  username: z.string(),
  email: z.string(),
  createdAt: z.string(),
  role: z.string(),
  profile: ProfileSchema,
});

type RootObject = z.infer<typeof RootObjectSchema>;

Notice the last line: z.infer derives the TypeScript type directly from the schema. You get both compile-time type safety and runtime validation from a single source of truth.

Using it at the fetch boundary looks like this:

const rawData = await response.json();
const user = RootObjectSchema.parse(rawData); // throws if shape is wrong

// Or use safeParse to avoid throwing:
const result = RootObjectSchema.safeParse(rawData);
if (!result.success) {
  console.error(result.error.issues);
} else {
  console.log(result.data.username); // fully typed
}

Adjust the generated schema to handle the nullable cases the generator can’t infer from a single sample:

avatar: z.string().nullable(), // was z.null()
bio: z.string().optional(),    // if the field might be absent

interface vs type: Which Should You Use?

两者相同 interfacetype aliases can represent object shapes in TypeScript, and for most API response typing they’re interchangeable. The practical differences:

  • Interfaces can be extended and merged — useful if you want to augment a base type across files. Declaration merging lets you add fields to an interface defined elsewhere.
  • Type aliases are more flexible — they can represent unions, intersections, tuples, and mapped types, which interfaces can’t.
  • Error messages tend to be clearer with interfaces — TypeScript expands type aliases in error output, which can make deeply nested errors harder to read.

For API response shapes, either works. Pick interface if you anticipate extending the type; use type if you need union or intersection semantics. Consistency within a codebase matters more than which one you choose.

The Practical Workflow

Here’s a workflow that takes minutes instead of an afternoon:

  1. Grab a real response. Use your browser’s DevTools network tab, Postman, or curl to capture an actual API response. The more complete the sample, the better the generated types.
  2. Generate the TypeScript interface. Paste the JSON into the JSON 转 TypeScript 接口生成器. Copy the output into your project.
  3. Generate the Zod schema. Paste the same JSON into the JSON 到 Zod Schema 生成器. Copy that into your project as well.
  4. Review for nullable and optional fields. Scan the generated output for fields typed as literal null or fields that could be absent. Update those to string | null, .nullable(), 或者 .optional() 根据需要。
  5. Validate at the fetch boundary. Replace any as YourType casts with YourSchema.parse()safeParse(). Now the type is guaranteed to match, not just assumed.

That’s the full loop — from raw JSON to compile-time safety and runtime guarantees in a few minutes.

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?