Все статьи
Форматы данных

JSON в TypeScript интерфейсы: автоматическая генерация

Как сгенерировать TypeScript типы из JSON, инструменты, best practices для типизации API ответов.

4 февраля 2025
8 мин чтения
ConvertHub
#typescript#json#типы

Введение

TypeScript победил в экосистеме JavaScript во многом благодаря строгой типизации. Но при работе с внешними API часто возникает проблема: сервер отдаёт JSON, а на клиенте приходится вручную описывать интерфейсы. Это рутинная и ошибкоопасная работа — пропущенное поле или неверный тип приводят к багам в рантайме. К счастью, TypeScript интерфейсы можно сгенерировать из JSON автоматически. В этой статье разберём подходы, инструменты и лучшие практики генерации типов из JSON-ответов API.

Зачем генерировать типы из JSON

Ручное описание типов для больших API занимает часы и быстро устаревает. Бэкенд меняет структуру ответа, а фронтенд-разработчик узнаёт об этом только из баг-репорта. Автоматическая генерация типов TypeScript решает обе проблемы: типы появляются за секунды, а при изменении API их легко регенерировать и сразу увидеть расхождения в коде.

Дополнительные плюсы:

  • Компилятор ловит обращения к несуществующим полям на этапе сборки.
  • IDE предлагает автодополнение по реальным полям API.
  • Ревью кода ускоряется: типы служат документацией контракта.
  • Упрощается рефакторинг: при изменении API компилятор подсветит все затронутые места.

Что такое TypeScript интерфейсы и type aliases

В TypeScript есть два основных способа описать форму объекта: interfaceи type. Интерфейсы расширяемы и удобны для описания объектов, type aliases гибче — поддерживают объединения (union), пересечения (intersection) и условные типы. Большинство генераторов используют interface для объектов иtype для скаляров и объединений.

Пример простого JSON и соответствующих типов:

// JSON
{
  "id": 42,
  "name": "Анна",
  "email": "anna@example.com",
  "roles": ["admin", "editor"],
  "active": true
}

// Сгенерированный TypeScript
interface Root {
  id: number;
  name: string;
  email: string;
  roles: string[];
  active: boolean;
}

Подходы к генерации

Из конкретного JSON-образца

Самый простой подход — взять реальный JSON-ответ и сгенерировать типы по нему. Это полезно, когда нет формальной схемы API, но есть примеры ответов. Минус: типы отражают только то, что есть в образце. Если в другом ответе поле может бытьnull или отсутствовать, это не попадёт в тип.

Для этого сценария используйте наш инструмент JSON в TypeScript: вставляете JSON, получаете готовые интерфейсы.

Из JSON Schema

Если API описано через OpenAPI или JSON Schema, типы лучше генерировать из схемы, а не из образца. Схема явно описывает необязательные поля, возможные типы (например,string | null) и ограничения. Популярный инструмент —json-schema-to-typescript.

Из OpenAPI / Swagger

Для REST API с OpenAPI-спецификацией применяют openapi-typescript илиswagger-typescript-api. Они генерируют не только типы моделей, но и типизированные клиентские функции для эндпоинтов.

Из GraphQL schema

В GraphQL-проектах используют graphql-codegen, который генерирует типы прямо из запросов и схемы. Это самый продвинутый сценарий: типы строго соответствуют конкретным запросам.

Пример: ручная генерация из JSON

Чтобы понять механику, посмотрим, как генератор превращает JSON в типы. Алгоритм:

  1. Если значение — примитив, ставим соответствующий тип: string, number, boolean, null.
  2. Если объект — создаём интерфейс с полями по свойствам.
  3. Если массив — определяем тип элемента и оборачиваем в [].
  4. Имена интерфейсов генерируем из контекста: ключа родительского поля или индекса массива.

Пример посложнее с вложенными объектами:

// JSON
{
  "user": {
    "id": 42,
    "name": "Анна",
    "address": {
      "city": "Москва",
      "zip": "101000"
    }
  }
}

// Сгенерированный TypeScript
interface Address {
  city: string;
  zip: string;
}

interface User {
  id: number;
  name: string;
  address: Address;
}

interface Root {
  user: User;
}

Обработка неоднозначностей

Пустые массивы

Если в JSON массив пуст, тип элемента неопределён. Генератор обычно ставитany[] или never[], что неудобно. Решение: предоставить образец с непустым массивом или вручную уточнить тип после генерации.

Необязательные поля

В одном ответе поле есть, в другом — отсутствует. По образцу генератор не сможет это определить. Для таких случаев нужна JSON Schema с required, или разработчик должен вручную добавить ? к необязательным полям.

Объединения типов

Если поле может быть строкой или числом, по одному образцу этого не понять. Опять же, помогает JSON Schema с type: ["string", "number"]. Без схемы генератор выберет тот тип, который увидел в образце.

Имена типов

Имена интерфейсов генерируются по имени корневого ключа. Если в JSON нет ключа (например, массив на верхнем уровне), имя будет Root или Item. Лучше давать осмысленные имена сразу, чтобы типы читаемо выглядели в коде.

Инструменты генерации

Онлайн-генератор

Для разовых задач удобен JSON в TypeScript от ConvertHub. Вставляете JSON, выбираете стиль (interface или type), получаете код. Всё работает локально в браузере, без отправки данных на сервер.

Командные инструменты

  • quicktype — мощный CLI, поддерживает множество языков: TypeScript, Rust, Go, Java, C#. Умеет читать JSON-файлы и генерировать типы по ним.
  • json-schema-to-typescript — генерация из JSON Schema. Лучший выбор, если у вас есть формальное описание API.
  • openapi-typescript — для OpenAPI-спецификаций. Генерирует типы моделей и эндпоинтов.
  • dtsgenerator — универсальный генератор из JSON Schema, OpenAPI, GraphQL.

Пример с quicktype

# Установка
npm install -g quicktype

# Генерация из JSON-файла
quicktype user.json -o user.ts --lang typescript

# Генерация из URL
quicktype https://api.example.com/users/1 -o User.ts

Лучшие практики

  • Регенерируйте типы при изменении API — добавьте шаг в CI, чтобы типы не расходились с реальным API.
  • Не редактируйте сгенерированные файлы вручную — помечайте их комментарием // @generated и при изменении API перегенерируйте.
  • Используйте образцы с максимальным набором полей — чтобы типы покрыли все возможные свойства.
  • Храните типы рядом с клиентом API — в отдельной папкеapi/types или src/generated.
  • Валидируйте типы в рантайме — TypeScript-типы существуют только при компиляции. Для защиты от некорректных ответов используйте zod, io-ts или class-validator.
  • Документируйте источники типов — комментарий со ссылкой на эндпоинт или схему поможет коллегам.

Валидация в рантайме

TypeScript-типы стираются при компиляции, поэтому если сервер вернёт неожиданную структуру, типы не помогут. Для критичных интеграций используют библиотеки валидации, которые одновременно проверяют данные в рантайме и выводят TypeScript-типы. Самый популярный вариант — zod:

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  roles: z.array(z.string()),
  active: z.boolean(),
});

type User = z.infer<typeof UserSchema>;

// Парсинг с валидацией
const user = UserSchema.parse(await response.json());

Такой подход объединяет статическую типизацию и runtime-валидацию, что критично для публичных API, где структура ответа может меняться без уведомления.

Сравнение с генерацией для других языков

Подобные инструменты существуют и для других языков: JSON в C# классы для .NET-разработчиков, генерация Go-структур, Kotlin data-классов. Принципы схожи: образец → типы. Если вы работаете в полиглот-проекте, держите типы для разных языков рядом, чтобы упростить поддержку контракта.

Расширенные сценарии

В реальных проектах генерация типов редко ограничивается простым JSON. Часто нужно учитывать особенности API: пагинацию, ошибки, контракты с разными версиями. Для API с пагинатором имеет смысл создать общий тип-обёртку, параметризованный типом элемента:

interface PaginatedResponse<T> {
  data: T[];
  page: number;
  pageSize: number;
  total: number;
}

type UsersResponse = PaginatedResponse<User>;

Если API возвращает разные структуры в зависимости от статуса (успех/ошибка), используйте discriminated unions. Это позволяет TypeScript сужать тип на основе значения поля-дискриминатора:

type ApiResponse =
  | { status: 'success'; data: User[] }
  | { status: 'error'; error: string; code: number };

function handleResponse(resp: ApiResponse) {
  if (resp.status === 'success') {
    // TypeScript знает, что resp.data существует
    console.log(resp.data);
  } else {
    // resp.error и resp.code доступны
    console.error(resp.error);
  }
}

Для API с версиями полезно хранить типы в отдельных папках (types/v1,types/v2) и переключаться между ними через alias в tsconfig. Это упрощает поддержку нескольких версий API и плавную миграцию.

Генерация клиента API

Помимо типов, генерация может включать клиентский код: функции для вызова эндпоинтов с типизированными параметрами и возвращаемыми значениями. Инструменты вродеopenapi-typescript-codegen, orval, swagger-typescript-apiсоздают готовый клиент на основе OpenAPI-спецификации. Это сильно ускоряет интеграцию: вместо ручного написания fetch-вызовов вы получаете типизированный API-клиент.

import { getUser, updateUser } from './api-client';

const user = await getUser(42);  // тип User
await updateUser(42, { name: 'Анна' });  // типизированный payload

Такие клиенты часто поддерживают интеграцию с React Query, SWR, Vue Query. Например,orval генерирует готовые хуки для React Query, что позволяет начать работу с API буквально за минуты. Главное — поддерживать спецификацию OpenAPI в актуальном состоянии и регенерировать клиент при каждом изменении.

Комбинирование типов и валидации

Современный подход — использовать библиотеки, которые объединяют статическую типизацию и runtime-валидацию. Помимо zod, упомянутого ранее, популярныio-ts (функциональный стиль), class-validator (для NestJS),ajv (быстрая валидация по JSON Schema). Все они позволяют описать схему данных один раз и получить как TypeScript-типы, так и функцию валидации.

В критичных по производительности местах ajv компилирует JSON Schema в JavaScript-функцию, что делает валидацию очень быстрой. Для типизации используется связка json-schema-to-typescript. Это даёт лучшее из двух миров: скорость ajv и удобство TypeScript-типов.

Также стоит упомянуть typebox — библиотеку от автора Sinclair, которая описывает типы как JSON Schema, но с TypeScript-friendly API. Типы, сгенерированные через TypeBox, можно использовать как для статической проверки, так и для runtime-валидации.

Тестирование сгенерированных типов

TypeScript-типы существуют только во время компиляции, поэтому их нельзя протестировать привычным образом. Однако есть инструменты статической проверки типов:tsd и expect-type позволяют писать утверждения о типах в виде тестов. Например, проверить, что функция возвращает именно User, а не any.

import { expectType } from 'tsd';
import { getUser } from './api';

expectType&lt;Promise&lt;User&gt;&gt;(getUser(42));

Это полезно при разработке генераторов типов: можно автоматизированно проверить, что генератор выдаёт ожидаемые типы для разных входных данных. CI должен запускать такие проверки наравне с unit-тестами.

Заключение

Автоматическая генерация типов TypeScript из JSON — не роскошь, а необходимость для серьёзных проектов. Она экономит время, снижает количество багов и упрощает синхронизацию с API. Для разовых задач используйте нашонлайн-генератор, для регулярной работы — quicktype или связку с JSON Schema/OpenAPI. И не забывайте про runtime-валидацию: TypeScript-типы защищают только на этапе разработки, а в продакшне нужна явная проверка данных.

Попробуйте эти инструменты

Похожие статьи