← Назад к вопросам

Как структуру данных полученных в JSON привязать к типам в TypeScript?

2.0 Middle🔥 221 комментариев
#JavaScript Core#Браузер и сетевые технологии

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Привязка JSON-данных к типам TypeScript

Работа с данными в формате JSON и их типизация в TypeScript — критически важный навык для Frontend Developer. Вот основные подходы и практики, которые я применяю в проектах.

Базовые подходы к типизации

1. Ручное определение интерфейсов/типов

Самый распространённый и надёжный способ — явное описание структуры данных:

// Определяем интерфейс для ожидаемой структуры
interface UserProfile {
  id: number;
  username: string;
  email: string;
  preferences?: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
  createdAt: string; // ISO date string
}

// Типизируем функцию получения данных
async function fetchUserProfile(userId: number): Promise<UserProfile> {
  const response = await fetch(`/api/users/${userId}`);
  const data = await response.json();
  
  // TypeScript считает data типом UserProfile,
  // но это утверждение (assertion), а не проверка
  return data as UserProfile;
}

2. Использование утилит TypeScript

Для работы с частичными или составными типами полезны встроенные утилиты:

// Базовый тип
type ApiResponse = {
  status: 'success' | 'error';
  data: unknown;
  timestamp: string;
};

// Создаём производные типы
type SuccessResponse<T> = ApiResponse & {
  status: 'success';
  data: T;
};

type ErrorResponse = ApiResponse & {
  status: 'error';
  error: string;
};

// Обобщённая функция с проверкой
async function fetchTypedData<T>(url: string): Promise<SuccessResponse<T>> {
  const response = await fetch(url);
  const result: ApiResponse = await response.json();
  
  if (result.status === 'error') {
    throw new Error((result as ErrorResponse).error);
  }
  
  return result as SuccessResponse<T>;
}

Продвинутые техники

3. Схемы валидации с использованием библиотек

На практике я предпочитаю runtime-валидацию с одновременной типизацией. Вот как это работает с Zod:

import { z } from 'zod';

// Определяем схему валидации
const UserSchema = z.object({
  id: z.number().int().positive(),
  username: z.string().min(3).max(50),
  email: z.string().email(),
  profile: z.object({
    avatar: z.string().url().optional(),
    bio: z.string().max(500).optional()
  }).optional(),
  roles: z.array(z.enum(['user', 'admin', 'moderator'])).default(['user'])
});

// Автоматически выводим TypeScript-тип
type User = z.infer<typeof UserSchema>;

// Функция с полной валидацией
async function loadAndValidateUser(userId: number): Promise<User> {
  const response = await fetch(`/api/users/${userId}`);
  const jsonData = await response.json();
  
  // Валидация данных в runtime
  const result = UserSchema.safeParse(jsonData);
  
  if (!result.success) {
    console.error('Validation errors:', result.error.errors);
    throw new Error('Invalid user data received');
  }
  
  return result.data; // Полностью типизированные данные
}

4. Генерация типов из JSON Schema или API спецификаций

В крупных проектах эффективно использовать автоматическую генерацию:

// Пример использования OpenAPI генерации
// tools.quicktype.io или swagger-codegen

// После генерации получаем готовые типы:
// import { User, Product, Order } from './generated-types';

Практические рекомендации

Маркеры для ненадёжных данных

Я всегда различаю данные из разных источников:

// Данные из API - требующие валидации
type Untrusted<T> = T;

// Валидированные данные
type Validated<T> = T & { __validated: true };

function asValidated<T>(data: T): Validated<T> {
  return { ...data, __validated: true } as Validated<T>;
}

Стратегии обработки ошибок

Всегда учитываю возможность несоответствия данных:

// Обёртка с безопасной обработкой
async function safeFetch<T>(
  url: string, 
  validator: (data: unknown) => data is T
): Promise<T | null> {
  try {
    const response = await fetch(url);
    const data = await response.json();
    
    if (validator(data)) {
      return data;
    }
    
    console.warn('Data validation failed for:', url);
    return null;
  } catch (error) {
    console.error('Fetch failed:', error);
    return null;
  }
}

// Пользовательский type guard
function isUserData(data: unknown): data is UserProfile {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    'username' in data
  );
}

Ключевые выводы

  1. Не доверяйте данным извне — TypeScript-типы существуют только на этапе компиляции
  2. Комбинируйте статическую и динамическую типизацию — используйте библиотеки валидации
  3. Создавайте иерархию типов — базовые типы, DTO, внутренние представления
  4. Документируйте изменения API — используйте семантическое версионирование для типов
  5. Тестируйте граничные случаи — особенно частичные данные и null/undefined значения

В production-проектах я обычно создаю отдельный слой Data Access Layer, который инкапсулирует всю логику получения, валидации и преобразования данных с полной TypeScript-типизацией. Это позволяет сохранять type safety на всём пути от API до UI компонентов.

Как структуру данных полученных в JSON привязать к типам в TypeScript? | PrepBro