広告が嫌いですか? 行く 広告なし 今日

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スキーマジェネレーター 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スキーマジェネレーター. 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 拡張機能

スコアボードが到着しました!

スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!

ニュースコーナー 技術ハイライト付き

参加する

価値ある無料ツールの提供を継続するためにご協力ください

コーヒーを買って