TypeScript - Как определить модель в сочетании с использованием мангуста?

Фон

Я использую mongoose и TypeScript в своем приложении Node.JS. Я использую мангуста, populate кучу мест при получении данных из базы данных.

Проблема, с которой я сталкиваюсь, заключается в том, что я не знаю, как печатать мои модели, чтобы свойство могло быть либо ObjectId, либо заполнено данными из другой коллекции.

Что я пробовал

Я попытался использовать типы объединений в своем определении типа модели, что похоже на то, что предлагает TypeScript для покрытия таких вещей:

interface User extends Document {
    _id: Types.ObjectId;
    name: string
}

interface Item extends Document {
    _id: Types.ObjectId;

    // Union typing here
    user: Types.ObjectId | User;
}

Моя схема определяет свойство только как ObjectId с ref.

const ItemSchema = new Schema({
    user: { type: Schema.Types.ObjectId, ref: "User", index: true }
})

Пример:

Так что я мог бы сделать что-то вроде этого:

ItemModel.findById(id).populate("user").then((item: Item) => {
    console.log(item.user.name);
})

Что приводит к ошибке компиляции:

[ts] Property 'name' does not exist on type 'User | ObjectId'.
     Property 'name' does not exist on type 'ObjectId'.

Вопрос

Как я могу иметь свойство модели, которое может быть одним из двух типов в TypeScript?

Ответ 1

Вам нужно использовать Types.ObjectId | User типа, чтобы сузить тип от Types.ObjectId | User Types.ObjectId | User для User...

if (item.user instanceof User) {
    console.log(item.user.name);
} else {
    // Otherwise, it is a Types.ObjectId
}

Если у вас есть структура, которая соответствует User, но не экземпляр, вам понадобится специальный тип guard:

function isUser(obj: User | any) : obj is User {
    return (obj && obj.name && typeof obj.name === 'string');
}

Что вы можете использовать с:

if (isUser(item.user)) {
    console.log(item.user.name);
} else {
    // Otherwise, it is a Types.ObjectId
}

Ответ 2

Приведите item.user к User, когда пользователь заполнится.

ItemModel.findById(id).populate("user").then((item: Item) => {
    console.log((<User>item.user).name);
})