Работа с JSON в TypeScript: парсинг и сериализация на практике

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

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

Начать курс

Введение: JSON и TypeScript — идеальная пара для работы с данными

JSON (JavaScript Object Notation) — это легковесный формат обмена данными, который стал стандартом де-факто для взаимодействия между клиентом и сервером, а также для хранения конфигураций и передачи структурированной информации. TypeScript, будучи строго типизированным надмножеством JavaScript, предоставляет мощные инструменты для безопасной и предсказуемой работы с JSON. В этой статье мы на практике разберём, как правильно парсить (преобразовывать строку JSON в объект) и сериализовать (преобразовывать объект в строку JSON) данные в TypeScript, избегая типичных ошибок и используя возможности системы типов.

Основы: JSON.parse() и JSON.stringify() в TypeScript

TypeScript полностью наследует встроенные методы JavaScript для работы с JSON: JSON.parse() и JSON.stringify(). Однако их использование в строго типизированной среде требует особого подхода.

JSON.parse() — из строки в объект

Метод JSON.parse() принимает строку JSON и возвращает объект. В TypeScript результат по умолчанию имеет тип any, что лишает нас всех преимуществ типизации. Рассмотрим пример:

const jsonString = '{"name": "TypeScript", "version": 5.3}';
const parsed: any = JSON.parse(jsonString);
console.log(parsed.name); // Работает, но нет автодополнения и проверки типов

Чтобы получить типизированный результат, используйте приведение типов (type assertion):

interface Language {
    name: string;
    version: number;
}

const jsonString = '{"name": "TypeScript", "version": 5.3}';
const parsed = JSON.parse(jsonString) as Language;
console.log(parsed.name.toUpperCase()); // Автодополнение работает!

JSON.stringify() — из объекта в строку

JSON.stringify() сериализует объект в строку JSON. TypeScript проверяет, что переданный объект является валидным для сериализации (не содержит функций, undefined, Symbol и т.д.).

interface User {
    id: number;
    name: string;
    isActive: boolean;
}

const user: User = { id: 1, name: "Alice", isActive: true };
const jsonString = JSON.stringify(user);
console.log(jsonString); // '{"id":1,"name":"Alice","isActive":true}'

Продвинутые техники: типизация и обработка ошибок

На практике работа с JSON редко бывает идеальной. Данные могут прийти с сервера в неожиданном формате, содержать лишние поля или отсутствовать обязательные. Рассмотрим, как TypeScript помогает справляться с этими проблемами.

Создание обобщённой функции для безопасного парсинга

Напишем универсальную функцию, которая принимает строку JSON и возвращает типизированный результат с обработкой ошибок:

function safeParse<T>(json: string): T | null {
    try {
        return JSON.parse(json) as T;
    } catch (error) {
        console.error("Ошибка парсинга JSON:", error);
        return null;
    }
}

// Использование
interface Product {
    id: number;
    title: string;
    price: number;
}

const rawData = '{"id": 101, "title": "Laptop", "price": 999.99}';
const product = safeParse<Product>(rawData);
if (product) {
    console.log(product.title); // "Laptop"
}

Валидация данных с помощью пользовательских охранников типа

Даже после приведения типа as T TypeScript не проверяет, что данные действительно соответствуют интерфейсу. Для runtime-валидации используйте type guards:

function isProduct(obj: any): obj is Product {
    return (
        typeof obj === 'object' &&
        obj !== null &&
        typeof obj.id === 'number' &&
        typeof obj.title === 'string' &&
        typeof obj.price === 'number'
    );
}

const rawData = '{"id": "abc", "title": "Phone", "price": 599}';
const parsed = JSON.parse(rawData);
if (isProduct(parsed)) {
    console.log(parsed.price.toFixed(2)); // Безопасно
} else {
    console.error("Некорректный формат данных");
}

Работа со сложными структурами: вложенные объекты и массивы

Реальные данные часто содержат вложенные объекты и массивы. TypeScript позволяет точно описать такие структуры.

Парсинг массива объектов

interface Comment {
    id: number;
    text: string;
    author: string;
}

interface Post {
    id: number;
    title: string;
    comments: Comment[];
}

const jsonString = `{
    "id": 1,
    "title": "TypeScript Guide",
    "comments": [
        {"id": 101, "text": "Great article!", "author": "Bob"},
        {"id": 102, "text": "Very useful", "author": "Jane"}
    ]
}`;

const post = JSON.parse(jsonString) as Post;
post.comments.forEach(comment => {
    console.log(`${comment.author}: ${comment.text}`);
});

Сериализация с форматированием и фильтрацией

JSON.stringify() принимает дополнительные параметры: replacer (функция или массив для фильтрации полей) и space (количество пробелов для отступов).

interface Config {
    apiKey: string;
    baseUrl: string;
    timeout: number;
    debug: boolean;
}

const config: Config = {
    apiKey: "secret-123",
    baseUrl: "https://api.example.com",
    timeout: 5000,
    debug: false
};

// Исключаем apiKey из сериализации
const safeConfig = JSON.stringify(config, ["baseUrl", "timeout", "debug"], 2);
console.log(safeConfig);
/* Вывод:
{
  "baseUrl": "https://api.example.com",
  "timeout": 5000,
  "debug": false
}
*/

Практический пример: интеграция с REST API

Рассмотрим реальный сценарий — получение данных с сервера и их обработка. Используем fetch и типизацию.

interface Todo {
    userId: number;
    id: number;
    title: string;
    completed: boolean;
}

async function fetchTodos(): Promise<Todo[]> {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/todos');
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data: Todo[] = await response.json() as Todo[];
        return data;
    } catch (error) {
        console.error("Ошибка загрузки задач:", error);
        return [];
    }
}

// Использование
(async () => {
    const todos = await fetchTodos();
    const incompleteTodos = todos.filter(todo => !todo.completed);
    console.log(`Невыполненных задач: ${incompleteTodos.length}`);
})();

                
                

Блоги

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