Avatar ·

Как преобразовать JSON объект в экземпляр класса TypeScript?

Всем привет! Я новичок в TypeScript и столкнулся с проблемой при работе с JSON данными. У меня есть класс User, который содержит поля и методы: ```typescript class User { constructor( public id: number, public name: string, public email: string, public createdAt: Date ) {} getDisplayName(): string { return `${this.name} (${this.email})`; } } ``` Я получаю данные от API в формате JSON: ```typescript const jsonData = '{"id": 1, "name": "Иван", "email": "ivan@example.com", "createdAt": "2024-01-15T10:30:00Z"}'; const parsedData = JSON.parse(jsonData); ``` Когда я пытаюсь использовать parsedData как экземпляр User, я не могу вызвать метод getDisplayName(), потому что TypeScript не создаёт настоящий объект класса. parsedData — это просто обычный объект с теми же полями. Я пробовал делать так: ```typescript const user: User = JSON.parse(jsonData); ``` Но это только заставляет TypeScript думать, что объект соответствует типу, но на самом деле это не экземпляр класса. Методы класса недоступны. Как правильно преобразовать JSON объект в настоящий экземпляр класса TypeScript, чтобы я мог использовать его методы? Есть ли какой-то стандартный подход или паттерн для этого? Заранее спасибо за помощь! <<>>

Проблема: JSON.parse() создаёт обычные объекты, а не экземпляры класса

Когда вы используете JSON.parse(), вы получаете простой JavaScript объект, который не является экземпляром вашего класса. TypeScript только проверяет типы на этапе компиляции, но в runtime это обычный объект без методов класса. Вот несколько проверенных способов решения этой проблемы.

Способ 1: Фабричный метод (рекомендуемый)

Самый чистый подход — добавить статический метод в класс, который принимает JSON объект и возвращает экземпляр класса:

class User {
  constructor(
    public id: number,
    public name: string,
    public email: string,
    public createdAt: Date
  ) {}

  // Фабричный метод для создания из JSON
  static fromJSON(json: any): User {
    return new User(
      json.id,
      json.name,
      json.email,
      new Date(json.createdAt)
    );
  }

  getDisplayName(): string {
    return `${this.name} (${this.email})`;
  }
}

// Использование
const jsonData = '{"id": 1, "name": "Иван", "email": "ivan@example.com", "createdAt": "2024-01-15T10:30:00Z"}';
const parsedData = JSON.parse(jsonData);
const user = User.fromJSON(parsedData);

console.log(user.getDisplayName()); // "Иван (ivan@example.com)"
console.log(user instanceof User); // true

Способ 2: Конструктор с Object.assign()

Можно использовать конструктор, который копирует свойства из обычного объекта:

class User {
  constructor(data: Partial<User>) {
    Object.assign(this, data);
    // Преобразуем строку в Date, если нужно
    if (typeof this.createdAt === 'string') {
      this.createdAt = new Date(this.createdAt);
    }
  }

  getDisplayName(): string {
    return `${this.name} (${this.email})`;
  }
}

// Использование
const jsonData = '{"id": 1, "name": "Иван", "email": "ivan@example.com", "createdAt": "2024-01-15T10:30:00Z"}';
const user = new User(JSON.parse(jsonData));

console.log(user.getDisplayName()); // "Иван (ivan@example.com)"

Способ 3: Использование библиотеки class-transformer (для сложных случаев)

Если у вас много вложенных объектов или сложные преобразования, используйте библиотеку class-transformer:

import { plainToClass } from 'class-transformer';

class User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;

  getDisplayName(): string {
    return `${this.name} (${this.email})`;
  }
}

const jsonData = '{"id": 1, "name": "Иван", "email": "ivan@example.com", "createdAt": "2024-01-15T10:30:00Z"}';
const user = plainToClass(User, JSON.parse(jsonData));

console.log(user.getDisplayName()); // "Иван (ivan@example.com)"
console.log(user instanceof User); // true

Важные моменты:

  • Всегда проверяйте типы — JSON может содержать невалидные данные. Используйте валидацию (например, библиотеку class-validator).
  • Обрабатывайте вложенные объекты — если у вас есть массивы или вложенные объекты, их тоже нужно преобразовывать рекурсивно.
  • Преобразуйте типы — даты, числа и другие специальные типы нужно явно преобразовывать из строк.
<<>>

Альтернативный подход: Использование интерфейсов вместо классов

Если вам не нужны методы класса, а только структура данных, рассмотрите использование интерфейсов вместо классов. Это проще и не требует преобразования:

interface IUser {
  id: number;
  name: string;
  email: string;
  createdAt: string; // или Date
}

// Функция для работы с данными
function getDisplayName(user: IUser): string {
  return `${user.name} (${user.email})`;
}

const jsonData = '{"id": 1, "name": "Иван", "email": "ivan@example.com", "createdAt": "2024-01-15T10:30:00Z"}';
const user: IUser = JSON.parse(jsonData);

console.log(getDisplayName(user)); // "Иван (ivan@example.com)"

Этот подход особенно полезен, когда данные приходят из внешних источников и не требуют сложной бизнес-логики. Вы также можете комбинировать интерфейсы с классами-помощниками для валидации и преобразования данных.