Как инициализировать объект TypeScript данными из JSON?
Проблема: JSON не знает о типах TypeScript
Вы столкнулись с фундаментальной особенностью: JSON — это текстовый формат, который содержит только примитивные типы (строки, числа, булевы значения, null, массивы и объекты). В нём нет дат, классов или пользовательских типов. Поэтому createdAt приходит как строка, а не как объект Date. Простое приведение типа через as User не сработает — это лишь подсказка компилятору, без реального преобразования данных.
Лучшее решение: фабричная функция с валидацией
Самый надёжный способ — написать функцию, которая принимает любой объект, проверяет его структуру и преобразует поля в нужные типы. Это даёт полный контроль и сохраняет типизацию.
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
createdAt: Date;
}
function createUser(data: unknown): User {
// Проверяем, что data — объект и не null
if (typeof data !== 'object' || data === null) {
throw new Error('Invalid user data: expected an object');
}
// Используем Record для доступа к полям
const obj = data as Record<string, unknown>;
// Проверяем каждое поле
if (typeof obj.id !== 'number') {
throw new Error('Invalid user data: id must be a number');
}
if (typeof obj.name !== 'string') {
throw new Error('Invalid user data: name must be a string');
}
if (typeof obj.email !== 'string') {
throw new Error('Invalid user data: email must be a string');
}
if (typeof obj.isActive !== 'boolean') {
throw new Error('Invalid user data: isActive must be a boolean');
}
if (typeof obj.createdAt !== 'string') {
throw new Error('Invalid user data: createdAt must be a string');
}
// Преобразуем строку даты в объект Date
const date = new Date(obj.createdAt);
if (isNaN(date.getTime())) {
throw new Error('Invalid user data: createdAt is not a valid date string');
}
// Возвращаем готовый объект
return {
id: obj.id,
name: obj.name,
email: obj.email,
isActive: obj.isActive,
createdAt: date,
};
}
// Использование:
const jsonData = {
id: 42,
name: "Иван Петров",
email: "ivan@example.com",
isActive: true,
createdAt: "2024-03-15T10:30:00Z"
};
const user = createUser(jsonData);
console.log(user.createdAt instanceof Date); // true
console.log(user.createdAt.toISOString()); // работает!
Альтернатива: библиотека Zod для валидации схем
Если у вас много сложных объектов, ручная проверка становится утомительной. Используйте библиотеку Zod — она позволяет объявить схему и автоматически проверять/преобразовывать данные.
import { z } from 'zod';
// Определяем схему
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
isActive: z.boolean(),
createdAt: z.string().transform((str) => new Date(str)),
});
// Тип выводится автоматически
type User = z.infer<typeof UserSchema>;
// Парсим JSON
const jsonData = {
id: 42,
name: "Иван Петров",
email: "ivan@example.com",
isActive: true,
createdAt: "2024-03-15T10:30:00Z"
};
const user = UserSchema.parse(jsonData);
console.log(user.createdAt instanceof Date); // true
Что выбрать?
- Фабричная функция — если проект небольшой, вы хотите минимум зависимостей и полный контроль.
- Zod — для больших проектов с множеством API, где важна автоматическая валидация и читаемость.
- Классы с конструктором — если вам нужны методы объекта (но помните: JSON не содержит методы, их нужно добавлять отдельно).
Главное правило: никогда не доверяйте данным из JSON. Всегда проверяйте их структуру и типы, особенно если данные приходят из внешнего источника.
<<Использование классов с конструктором
Если вам нужны не только данные, но и методы, можно создать класс, который принимает частичные данные и дополняет их:
class User {
id: number;
name: string;
email: string;
isActive: boolean;
createdAt: Date;
constructor(data: Partial<User>) {
this.id = data.id ?? 0;
this.name = data.name ?? '';
this.email = data.email ?? '';
this.isActive = data.isActive ?? false;
this.createdAt = data.createdAt instanceof Date
? data.createdAt
: new Date(data.createdAt ?? '');
}
getFormattedDate(): string {
return this.createdAt.toLocaleDateString('ru-RU');
}
}
// Использование:
const jsonData = {
id: 42,
name: "Иван Петров",
email: "ivan@example.com",
isActive: true,
createdAt: "2024-03-15T10:30:00Z"
};
const user = new User(jsonData);
console.log(user.getFormattedDate()); // "15.03.2024"
Когда это полезно?
- Если объект должен иметь поведение (методы).
- Если нужно установить значения по умолчанию для отсутствующих полей.
- Если вы используете DI-контейнеры или фреймворки, ожидающие классы.
Недостаток: нужно вручную писать конструктор для каждого класса. Для больших проектов лучше комбинировать с Zod или фабричной функцией из первого ответа.