JSON в TypeScript: 5 фатальных ошибок при касте и как их исправить

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

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

Начать курс

Введение: Миф о «магическом» приведении типов

Каждый TypeScript-разработчик рано или поздно сталкивается с ситуацией: вы получаете JSON с сервера, уверены, что знаете его структуру, и делаете простой cast через as. И… ничего не работает. Методы класса отсутствуют, поля undefined, а дебаг превращается в квест.

Вопрос «How do I cast a JSON object to a TypeScript class?» — один из самых популярных на Stack Overflow (десятки тысяч просмотров). И неудивительно. TypeScript — это надстройка, которая существует только во время компиляции. В runtime ваш «класс» — это просто plain JavaScript object.

В этой статье мы разберем 5 самых частых и опасных ошибок, которые совершают разработчики при работе с JSON и классами TypeScript. Вы узнаете, как их избежать, и получите готовые решения для production-кода.

Ошибка №1: Использование as для «настоящего» приведения

Самая распространенная ловушка — думать, что конструкция jsonData as MyClass создает экземпляр класса.

Что происходит на самом деле?

Оператор as (или angle-bracket синтаксис) — это type assertion. Он говорит компилятору: «Доверься мне, я знаю тип». Но в JavaScript он не выполняет никакой магии.

class User {
    name: string = '';
    greet(): string {
        return `Привет, ${this.name}!`;
    }
}

const rawData = JSON.parse('{"name": "Анна"}');
const user = rawData as User;

console.log(user.name); // "Анна" — работает
console.log(user.greet()); // TypeError: user.greet is not a function

Почему? Потому что rawData — это обычный объект { name: 'Анна' }. У него нет прототипа User.prototype, а значит, и метода greet.

Как исправить?

Всегда создавайте настоящий экземпляр класса. Базовый способ — Object.assign:

const user = Object.assign(new User(), rawData);
console.log(user.greet()); // "Привет, Анна!"

Но у этого метода есть нюансы, о которых мы поговорим дальше.

Ошибка №2: Игнорирование вложенных объектов и массивов

Вы радостно используете Object.assign, но ваши данные содержат вложенные структуры. И тут начинается ад.

class Address {
    city: string = '';
    zip: string = '';
}

class Company {
    name: string = '';
    address: Address = new Address();
}

const data = JSON.parse('{"name": "TechCorp", "address": {"city": "Москва", "zip": "101000"}}');
const company = Object.assign(new Company(), data);

console.log(company.address.city); // "Москва" — повезло?
console.log(company.address instanceof Address); // false!

Проблема: address остался простым объектом, а не экземпляром Address. Если у Address будут методы или computed properties, они потеряются.

Решение: Рекурсивное приведение

Пишем функцию-фабрику или используем библиотеку class-transformer:

import { plainToClass } from 'class-transformer';

const company = plainToClass(Company, data);
console.log(company.address instanceof Address); // true

class-transformer рекурсивно обходит все поля и создает правильные экземпляры классов на основе декораторов @Type.

Ошибка №3: Потеря значений по умолчанию

TypeScript позволяет задавать значения полей по умолчанию прямо в классе. Но при «касте» JSON эти значения могут быть перезаписаны undefined.

class Config {
    theme: string = 'light';
    fontSize: number = 14;
    language: string = 'ru';
}

const partialData = JSON.parse('{"theme": "dark"}');
const config = Object.assign(new Config(), partialData);

console.log(config.fontSize); // 14 — ок
console.log(config.language); // 'ru' — ок

Кажется, всё работает? Но что если в JSON придет поле со значением null?

const badData = JSON.parse('{"theme": "dark", "fontSize": null}');
const config = Object.assign(new Config(), badData);

console.log(config.fontSize); // null — а ожидали 14!

Как защититься?

Вариант 1: Фильтровать null перед assign:

function cleanAndAssign<T>(cls: new () => T, data: Partial<T>): T {
    const instance = new cls();
    Object.keys(data).forEach(key => {
        if (data[key as keyof T] !== null && data[key as keyof T] !== undefined) {
            (instance as any)[key] = data[key as keyof T];
        }
    });
    return instance;
}

Вариант 2 (профессиональный): Использовать class-validator + class-transformer для валидации и преобразования.

Ошибка №4: Забываем про даты и специальные типы

JSON не поддерживает тип Date. Все даты приходят как строки. Если вы ожидаете объект Date, то получите обычную строку.

class Event {
    title: string = '';
    date: Date = new Date();
}

const eventData = JSON.parse('{"title": "Встреча", "date": "2024-12-01T10:00:00Z"}');
const event = Object.assign(new Event(), eventData);

console.log(typeof event.date); // "string" — а не "object"
console.log(event.date instanceof Date); // false

Решение: Явное преобразование

Самый надежный способ — написать метод fromJSON внутри класса:

class Event {
    title: string = '';
    date: Date = new Date();

    static fromJSON(data: any): Event {
        const event = new Event();
        event.title = data.title;
        event.date = new Date(data.date);
        return event;
    }
}

const event = Event.fromJSON(eventData);
console.log(event.date instanceof Date); // true

Если используете class-transformer, добавьте декоратор @Type(() => Date).

Ошибка №5: Наивная вера в типы из сторонних API

Вы получили JSON от API, который «гарантированно» возвращает определённую структуру. Но реальность сурова: поля могут отсутствовать, типы могут не совпадать, а структура — меняться.

interface ApiResponse {
    id: number;
    name: string;
    email: string;
}

// Вдруг API вернул:
const badResponse = JSON.parse('{"id": "123", "name": null, "email": "test@test"}');
// id — строка, name — null. Ваш код упадет.

Стратегия защиты:

  • Всегда валидируйте входящие данные с помощью библиотек типа zod или io-ts.
  • Используйте стр

Блоги

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