§

JSON-образец

Подготовка инфереера типов…
§

TypeScript-интерфейсы

TypeScript

Российские TypeScript-команды сталкиваются с этой проблемой регулярно. Крупные SDK поставляют типизированные клиенты (Stripe, Twilio, AWS), но внутренние сервисы и сторонние webhook-payload'ы редко это делают. Типичный рабочий процесс: захватить реальный ответ на вкладке «Сеть», вставить его сюда, назвать корень по имени эндпоинта и скопировать вывод в каталог types проекта. Дальше строгий режим TypeScript поймает несоответствия, которые документация забыла упомянуть. Инфереер работает полностью в браузере, поэтому payload'ы со staging-API, подписанные тела webhook'ов и ответы до публичного релиза никогда не попадают на сторонний сервис.

Как работает вывод типов из JSON в TypeScript

Вывод — это единственный проход по разобранному JSON-дереву. Инструмент читает каждое значение, выбирает для него TypeScript-тип, а затем записывает по одному интерфейсу на каждый найденный объект.

  1. Разбор JSON-образца нативным парсером браузера с отклонением некорректного ввода с подсказкой строки/столбца.
  2. Определение TypeScript-типа для каждого значения: string, number, boolean, null, массив или вложенный объект.
  3. Присвоение каждому вложенному объекту имени интерфейса, производного от ключа родительского свойства (так, user.address становится интерфейсом Address).
  4. Объединение типов элементов по каждому массиву, чтобы список из {id: 1} и {id: 2, label: "x"} создал union с правильными optional-полями.
  5. Применение ваших настроек (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'а остаётся на вашей машине.