JSON обработка в TypeScript: от базового парсинга до строгой типизации
TypeScript, будучи надстройкой над JavaScript, наследует все встроенные методы работы с JSON, но добавляет к ним мощную систему типов. Однако стандартный JSON.parse() возвращает тип any, что сводит на нет все преимущества статической типизации. В этой статье мы разберем, как безопасно и эффективно обрабатывать JSON в TypeScript, используя как нативные возможности, так и специализированные библиотеки.
Проблема стандартного JSON.parse
Рассмотрим типичный сценарий:
const jsonString = '{"name":"Alice","age":30}';
const user: any = JSON.parse(jsonString); // тип any
console.log(user.name.toUpperCase()); // Ошибка во время выполнения, если name отсутствует
TypeScript не предупредит вас, если структура JSON не соответствует ожидаемой. Решение — использовать утверждение типа (type assertion) или, что лучше, — валидацию во время выполнения.
Базовое утверждение типа (Type Assertion)
Самый простой способ — явно указать тип результата:
interface User {
id: number;
name: string;
email?: string;
}
const raw = '{"id":1,"name":"Bob"}';
const user = JSON.parse(raw) as User;
console.log(user.name); // TypeScript знает, что это string
Минус: если JSON придет с другими полями, ошибка возникнет только в рантайме. Это небезопасно при работе с внешними API.
Рекурсивные и условные типы для JSON
TypeScript позволяет описать любой JSON через рекурсивный тип:
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
function safeParse(json: string): JSONValue | Error {
try {
return JSON.parse(json) as JSONValue;
} catch (e) {
return new Error('Invalid JSON');
}
}
Такой подход гарантирует, что результат всегда будет валидной JSON-структурой, но не проверяет конкретную форму.
Библиотека Zod — валидация схем во время выполнения
Zod — самая популярная библиотека для декларативной валидации данных в TypeScript. Она позволяет описать схему и получить тип из неё автоматически.
Установка
npm install zod
Пример использования
import { z } from 'zod';
// Описываем схему
const UserSchema = z.object({
id: z.number().positive(),
name: z.string().min(1),
email: z.string().email().optional(),
role: z.enum(['admin', 'user']).default('user'),
});
// Выводим тип из схемы
type User = z.infer<typeof UserSchema>;
// Безопасный парсинг
const result = UserSchema.safeParse(JSON.parse(rawJson));
if (result.success) {
// result.data имеет тип User
console.log(result.data.name);
} else {
console.error(result.error.format());
}
Zod автоматически проверяет типы, обязательность полей, значения по умолчанию и возвращает подробные ошибки. Это идеальный выбор для обработки данных от REST API или конфигурационных файлов.
Библиотека io-ts — функциональный подход
io-ts основана на функциональном программировании и библиотеке fp-ts. Она использует композицию кодеков (codecs) для декодирования/кодирования данных.
Пример
import * as t from 'io-ts';
import { isRight } from 'fp-ts/lib/Either';
const UserCodec = t.type({
id: t.number,
name: t.string,
tags: t.array(t.string),
});
const decoded = UserCodec.decode(JSON.parse(raw));
if (isRight(decoded)) {
// decoded.right — типизированный объект
console.log(decoded.right.name);
} else {
// decoded.left — ошибки валидации
console.log(decoded.left);
}
io-ts более строгий и математически корректный, но требует понимания функциональных концепций (Either, pipe).
Сериализация в JSON с TypeScript
Обратная задача — преобразовать объект в JSON-строку. Стандартный JSON.stringify() работает, но не учитывает типы TypeScript. Чтобы контролировать процесс, можно использовать метод toJSON() в классах:
class Product {
constructor(
public id: number,
public name: string,
private secretKey: string
) {}
toJSON() {
return {
id: this.id,
name: this.name,
// secretKey не попадает в JSON
};
}
}
const product = new Product(1, 'Laptop', 'hidden123');
console.log(JSON.stringify(product)); // {"id":1,"name":"Laptop"}
Работа с JSON Schema
Если вы используете OpenAPI или JSON Schema для