JSON в TypeScript интерфейсы: автоматическая генерация
Как сгенерировать TypeScript типы из JSON, инструменты, best practices для типизации API ответов.
Введение
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 в типы. Алгоритм:
- Если значение — примитив, ставим соответствующий тип:
string,number,boolean,null. - Если объект — создаём интерфейс с полями по свойствам.
- Если массив — определяем тип элемента и оборачиваем в
[]. - Имена интерфейсов генерируем из контекста: ключа родительского поля или индекса массива.
Пример посложнее с вложенными объектами:
// 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<Promise<User>>(getUser(42));Это полезно при разработке генераторов типов: можно автоматизированно проверить, что генератор выдаёт ожидаемые типы для разных входных данных. CI должен запускать такие проверки наравне с unit-тестами.
Заключение
Автоматическая генерация типов TypeScript из JSON — не роскошь, а необходимость для серьёзных проектов. Она экономит время, снижает количество багов и упрощает синхронизацию с API. Для разовых задач используйте нашонлайн-генератор, для регулярной работы — quicktype или связку с JSON Schema/OpenAPI. И не забывайте про runtime-валидацию: TypeScript-типы защищают только на этапе разработки, а в продакшне нужна явная проверка данных.
Попробуйте эти инструменты
Похожие статьи
JSON vs XML — какое выбрать для проекта
Сравнение JSON и XML: синтаксис, размер, скорость парсинга, читаемость. Когда JSON лучше, а когда XML.
JSON форматтер: зачем нужен и как использовать
Что такое форматирование JSON, отступы и пробелы, валидация, minify vs beautify, лучшие практики.
CSV в JSON: конвертация и когда нужна
Как преобразовать CSV в JSON, структура данных, обработка больших файлов, использование в JavaScript.
YAML — конфигурационный формат: полный гид
Синтаксис YAML, отступы, типы данных, отличие от JSON, использование в Docker, Kubernetes, CI/CD.