JSON обработка в TypeScript: от базовых операций до продвинутой типизации
JSON (JavaScript Object Notation) остаётся основным форматом обмена данными в веб-разработке. TypeScript добавляет к работе с JSON мощную систему типов, которая позволяет избежать множества ошибок на этапе компиляции. В этой статье мы разберём все аспекты обработки JSON в TypeScript: от простого парсинга до создания строго типизированных валидаторов.
Базовый парсинг и сериализация
Начнём с фундаментальных операций. Встроенные методы JSON.parse() и JSON.stringify() работают в TypeScript так же, как в JavaScript, но с важными отличиями в типизации.
// Простой парсинг — результат будет any
const data: any = JSON.parse('{"name": "Alice", "age": 30}');
// Лучше сразу указать тип
interface User {
name: string;
age: number;
}
const user: User = JSON.parse('{"name": "Alice", "age": 30}') as User;
console.log(user.name); // Alice
Важно понимать: as User — это приведение типа (type assertion), а не проверка. Если JSON содержит невалидные данные, TypeScript не предупредит вас об ошибке. Для безопасной работы нужна валидация.
Безопасный парсинг с помощью Generics
Создадим универсальную функцию для безопасного парсинга JSON с проверкой структуры:
function safeParse<T>(json: string, validator: (data: unknown) => data is T): T | null {
try {
const parsed: unknown = JSON.parse(json);
if (validator(parsed)) {
return parsed;
}
return null;
} catch {
return null;
}
}
// Определяем тип и функцию-валидатор
interface Product {
id: number;
title: string;
price: number;
}
function isProduct(data: unknown): data is Product {
if (typeof data !== 'object' || data === null) return false;
const obj = data as Record<string, unknown>;
return (
typeof obj.id === 'number' &&
typeof obj.title === 'string' &&
typeof obj.price === 'number'
);
}
const jsonString = '{"id": 1, "title": "Book", "price": 19.99}';
const product = safeParse<Product>(jsonString, isProduct);
if (product) {
console.log(`Товар: ${product.title}, цена: ${product.price}`);
} else {
console.log('Неверный формат JSON');
}
Работа с вложенными структурами и массивами
Реальные JSON-данные часто содержат вложенные объекты и массивы. Рассмотрим пример с типизацией сложной структуры:
interface Order {
id: string;
customer: {
name: string;
email: string;
};
items: Array<{
productId: number;
quantity: number;
}>;
total: number;
}
// Парсим и сразу проверяем структуру
function parseOrder(json: string): Order {
const raw = JSON.parse(json);
// Простейшая проверка
if (!raw.id || !raw.customer || !Array.isArray(raw.items)) {
throw new Error('Invalid order structure');
}
return raw as Order;
}
const orderJson = `{
"id": "ORD-001",
"customer": {"name": "Bob", "email": "bob@example.com"},
"items": [{"productId": 42, "quantity": 2}],
"total": 59.98
}`;
try {
const order = parseOrder(orderJson);
console.log(`Заказ ${order.id} на сумму ${order.total}`);
} catch (e) {
console.error('Ошибка парсинга:', e.message);
}
Сериализация с кастомными преобразователями
Иногда нужно контролировать, как объекты преобразуются в JSON. TypeScript позволяет создавать кастомные сериализаторы:
interface Config {
host: string;
port: number;
timeout: number; // в миллисекундах
}
function serializeConfig(config: Config): string {
return JSON.stringify({
host: config.host,
port: config.port,
timeout: config.timeout / 1000 // конвертируем в секунды
}, null, 2); // форматирование с отступами
}
const myConfig: Config = {
host: 'localhost',
port: 3000,
timeout: 5000
};
console.log(serializeConfig(myConfig));
// Вывод:
// {
// "host": "localhost",
// "port": 3000,
// "timeout": 5
// }
Продвинутая валидация с библиотеками
Для сложных проектов стоит рассмотреть специализированные библиотеки валидации. Две популярные опции:
- Zod — современная библиотека с декларативным синтаксисом и отличной интеграцией с TypeScript
- Yup — проверенная временем библиотека, часто используется с Formik
Пример с Zod:
import { z } from 'zod';
// Определяем схему валидации
const