Импорт JSON в TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как импортировать JSON в TypeScript и не сойти с ума

Если вы когда-нибудь пробовали сделать import data from './config.json' в TypeScript, то знаете это чувство: компилятор краснеет, ругается и выбрасывает ошибку Cannot find module. Знакомая боль? Ещё бы! Вопрос про импорт JSON в TypeScript — один из самых популярных на Stack Overflow. И это не удивительно: JSON используется повсюду — конфиги, локализация, мок-данные, статические списки. Но TypeScript по умолчанию не умеет работать с JSON-файлами.

В этом туториале мы разберём все способы импорта JSON в TypeScript — от простого включения флага в tsconfig до продвинутой типизации. Вы узнаете, как заставить TypeScript дружить с JSON, и больше никогда не увидите эту красную ошибку. Поехали!

Почему TypeScript не видит JSON?

Прежде чем нырять в код, давайте разберёмся в причине. TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию. По умолчанию компилятор TypeScript ожидает, что все импортируемые модули — это .ts или .js файлы. JSON — это не модуль, это формат данных. TypeScript просто не знает, как его интерпретировать.

Решение лежит в двух плоскостях:

  • Настройка компилятора — включить поддержку JSON через флаг resolveJsonModule.
  • Объявление модуля — вручную описать тип для JSON-файлов, если флаг не подходит.

Способ 1: Включаем resolveJsonModule (правильный путь)

Самый простой и рекомендуемый способ — добавить в tsconfig.json опцию resolveJsonModule. Это флаг, который говорит TypeScript: "Эй, дружище, давай будем импортировать JSON как обычные модули".

// tsconfig.json
{
    "compilerOptions": {
        "resolveJsonModule": true,
        "esModuleInterop": true
    }
}

После этого вы спокойно можете писать:

// config.ts
import config from './config.json';

console.log(config.apiUrl); // работает!
console.log(config.port);    // работает!

Что здесь происходит? TypeScript автоматически создаёт типы для импортируемого JSON. Если в вашем JSON есть поле apiUrl: string и port: number, TypeScript выведет их типы и будет проверять использование. Круто, правда?

Но есть нюанс: resolveJsonModule также требует включения esModuleInterop или allowSyntheticDefaultImports, чтобы импорт по умолчанию работал корректно. Без этого вы получите ошибку при попытке импорта.

Способ 2: Импорт с именованными экспортами

Иногда вам нужен не весь JSON, а только определённые поля. TypeScript поддерживает и это:

import { apiUrl, port } from './config.json';

console.log(apiUrl); // строка
console.log(port);   // число

Это возможно благодаря так называемому "tree-shaking" в сборщиках (Webpack, Vite), но на уровне TypeScript это просто деструктуризация объекта. Однако помните: если вы используете resolveJsonModule, TypeScript автоматически создаёт типы для всех полей, поэтому можно смело импортировать как весь объект, так и его части.

Способ 3: require вместо import (для старых проектов)

Если вы работаете в проекте, где resolveJsonModule по каким-то причинам включить нельзя (например, конфликт с другими настройками), можно использовать старый добрый require:

const config = require('./config.json');

console.log(config.apiUrl); // работает, но без типов

Проблема этого подхода очевидна: вы теряете типизацию. TypeScript не знает, что лежит внутри config, и будет считать его типом any. Это открывает дорогу ошибкам. Если уж используете require, хотя бы добавьте объявление типа:

interface Config {
    apiUrl: string;
    port: number;
}

const config: Config = require('./config.json');
console.log(config.apiUrl); // теперь с типом string

Способ 4: Создаём глобальное объявление модуля

Бывает, что вы не можете менять tsconfig.json (например, в библиотеке или monorepo). Тогда можно создать файл json.d.ts и написать в нём:

// json.d.ts
declare module "*.json" {
    const value: any;
    export default value;
}

Этот трюк говорит TypeScript: "Любой файл с расширением .json — это модуль, который экспортирует по умолчанию что-то (тип any)". Минус — вы снова теряете типизацию. Лучше сделать так:

// json.d.ts
declare module "*.json" {
    const value: Record<string, unknown>;
    export default value;
}

Хотя это всё равно не даст автодополнения для конкретных полей. Для полной типизации придётся либо генерировать типы динамически, либо использовать resolveJsonModule.

Типизация JSON: как сделать идеально

Допустим, у вас есть сложный JSON с вложенными объектами и массивами. resolveJsonModule выведет типы автоматически, но если вы хотите явно описать структуру, сделайте так:

// config.json
{
    "apiUrl": "https://api.example.com",
    "port": 8080,
    "features": {
        "darkMode": true,
        "notifications": false
    }
}

// config.ts
import config from './config.json';

interface Config {
    apiUrl: string;
    port: number;
    features: {
        darkMode: boolean;
        notifications: boolean;
    };
}

const typedConfig = config as Config;
console.log(typedConfig.features.darkMode); // true

Кастинг через as — это костыль, но он работает. Более элегантный способ — использовать satisfies (TypeScript 4.9+):

const typedConfig = config satisfies Config;

Это не меняет тип, но проверяет, что объект соответствует интерфейсу. Если в JSON появится лишнее поле — ошибки не будет, если не хватает обязательного — будет.

Реальный пример: конфиг приложения

Давайте соберём всё вместе. Представьте, что у вас есть файл app.config.json:

{
    "appName": "MySuperApp",
    "version": "2.0.1",
    "database": {
        "host": "localhost",
        "port": 5432,
        "user": "admin",
        "password": "secret"
    },
    "logging": {
        "level": "info",
        "file": "/var/log/app.log"
    }
}

Импортируем его в main.ts с полной типизацией:

import appConfig from './app.config.json';

interface DatabaseConfig {
    host: string;
    port: number;
    user: string;
    password: string;
}

interface LoggingConfig {
    level: string;
    file: string;
}

interface AppConfig                

Блоги

Рекомендаций по книгам