Как работает вывод типов из JSON в TypeScript
Вывод — это единственный проход по разобранному JSON-дереву. Инструмент читает каждое значение, выбирает для него TypeScript-тип, а затем записывает по одному интерфейсу на каждый найденный объект.
- Разбор JSON-образца нативным парсером браузера с отклонением некорректного ввода с подсказкой строки/столбца.
- Определение TypeScript-типа для каждого значения:
string,number,boolean,null, массив или вложенный объект. - Присвоение каждому вложенному объекту имени интерфейса, производного от ключа родительского свойства (так,
user.addressстановится интерфейсомAddress). - Объединение типов элементов по каждому массиву, чтобы список из
{id: 1}и{id: 2, label: "x"}создал union с правильными optional-полями. - Применение ваших настроек (interface vs. type, readonly, optional-nullable) и эмиссия объявлений в порядке зависимостей, чтобы файл компилировался без forward-ссылок.
Зачем генерировать TypeScript-типы из JSON?
- Большинство ошибок формы данных можно поймать во время компиляции, если тип ответа записан. Вывод интерфейса из реального payload'а делает большую часть работы за вас, а строгий режим TypeScript поймает поле, которое документация забыла упомянуть.
- Сочетание выведенных интерфейсов с runtime-валидатором вроде Zod или io-ts даёт одной и той же форме две задачи: автодополнение в редакторе при разработке и ошибку 400 на краю, когда продакшн пришлёт что-то неожиданное.
- Языковой сервер TypeScript показывает только известные ему поля. После импорта выведенного интерфейса автодополнение работает в момент ввода точки — никакого приведения через `as any` и разочарованного grep по всему репозиторию.
- Если вы собираетесь писать OpenAPI-спецификацию, выведенный интерфейс — это быстрый черновик схемы ответа. Примеры и ограничения всё равно нужно писать вручную, но имена свойств и типы уже верные.
Типичные применения
Вывод типов наиболее полезен, когда реальный payload существует, но схемы нет.
- Типизация сторонних webhook-payload'ов от Stripe, GitHub или Twilio перед написанием обработчика.
- Начальная загрузка типов для внутреннего REST API, чтобы фронтенд-команда могла начать писать код против него в тот же день, когда бэкенд будет готов.
- Генерация отправной точки для схемы Zod, io-ts или Valibot из наблюдаемого API-ответа.
Как выглядит вывод?
По JSON-образцу и имени корня генератор создаёт дерево интерфейсов — по одному на каждый вложенный объект. Для приведённого ниже ввода с именем корня User:
Вставьте {"id":1,"name":"Alice","tags":["a","b"],"address":{"city":"Paris"}} с именем корня User — генератор выдаст:
export interface User {
id: number;
name: string;
tags: string[];
address: Address;
}
export interface Address {
city: string;
}
Обратите внимание, что address был вынесен в собственный именованный интерфейс — это вывод в порядке зависимостей. Тот же JSON со стилем объявления type вместо interface выдаст export type User = {...}; с включённым переключателем readonly каждое свойство получит модификатор readonly.
Настройки генератора
Стиль объявления
Выберите interface (стандартная TypeScript-идиома для форм объектов) или type (удобно, если потребуются mapped-типы, conditional-типы или пересечения). Оба варианта имеют одинаковое runtime-поведение; выбор — это предпочтение стиля кода.
Optional nullable-поля
Когда выборочное значение равно null, тип поля становится T | null. Включение этой опции также добавляет модификатор ?, делая поле optional со стороны TypeScript — полезно, когда API иногда полностью пропускает ключ вместо того, чтобы вернуть null.
Модификатор readonly
Добавляет readonly перед каждым объявлением свойства, чтобы выведенный интерфейс соответствовал неизменяемой модели данных. Удобно для срезов Redux-состояния, замороженных API-ответов или везде, где нужно, чтобы компилятор указывал на случайные мутации.
Поддерживаются ли вложенные объекты и массивы?
Да. Каждый вложенный объект становится именованным интерфейсом, производным от ключа родительского свойства, а массивы выводят тип элемента из своего содержимого. Массивы объектов получают интерфейс на каждую форму объекта, а там, где формы не совпадают, — union-типы.
Как выводятся optional-поля?
Включите переключатель «Пометить nullable-поля как optional», и любое поле, чьё выборочное значение равно null, получит модификатор ? на ключе и | null в типе. Без переключателя поле остаётся обязательным, а тип — просто T | null.
Поддерживаются ли дискриминированные union-типы?
Базовые union-типы появляются, когда массив содержит элементы разной формы или поле несёт как значение, так и null. Полный вывод дискриминированных union-типов (выбор type или kind в качестве тега и разделение вариантов) требует нескольких образцов — это запланировано, но ещё не реализовано.
Можно ли выводить типы из нескольких JSON-образцов?
Пока нет — текущий инфереер читает один образец за раз. Если у вас есть два payload'а, которые должны разделять интерфейс (например, endpoint списка и endpoint отдельного элемента), практическое решение — объединить их в один массив, сгенерировать из него и затем переименовать результирующие union-типы. Вывод из нескольких образцов запланирован, поскольку это единственный способ обнаружить поля, присутствующие в одном ответе и отсутствующие в другом.
Вставьте payload, назовите корень, скопируйте интерфейсы. Весь пайплайн работает в вашем браузере, поэтому невыпущенный API или подписанное тело webhook'а остаётся на вашей машине.