访问数组中嵌入式鉴别器属性时触发错误
NestJS中Mongoose嵌入式鉴别器数组字段访问返回undefined问题
在NestJS应用中,Mongoose模式的数组内使用嵌入式鉴别器时,遍历数组访问鉴别器专属字段(如UpgradeAction的details),明明调试时能看到字段存在于内存中,但访问时始终返回undefined。
模式定义
User
@Schema() export class User { @Prop({ required: true, type: [BaseAction] }) subscriptionHistory: BaseAction[]; } export const UserSchema = SchemaFactory.createForClass(User); export type UserModel = Model<User>; export type UserDocument = HydratedDocument<User>;
BaseAction及鉴别器
@Schema({ _id: false, discriminatorKey: 'action', autoCreate: false }) export class BaseAction { @Prop({ required: true, type: String, enum: SubscriptionAction }) action: string; @Prop({ default: Date.now }) timestamp: Date; } export const BaseActionSchema = SchemaFactory.createForClass(BaseAction); @Schema({ _id: false }) export class UpgradeAction { action: string = SubscriptionAction.Upgrade; timestamp: Date; @Prop({ required: true, type: Object }) details: SubscriptionChange & StartDateChange & EndDateChange; } export const UpgradeActionSchema = SchemaFactory.createForClass(UpgradeAction); /* * 'details'属性使用的辅助类示例 */ @Schema({ _id: false }) class SubscriptionChange { @Prop({ required: true }) oldVersion: number; /* 更多属性 */ }
模块
@Module({ imports: [ MongooseModule.forFeature([ { name: User.name, schema: UserSchema }, { name: BaseAction.name, schema: UserSchema.path('subscriptionHistory').schema, discriminators: [ { name: SubscriptionAction.Upgrade, schema: UpgradeActionSchema }, /* 更多鉴别器 */ ], }, ]) ], /* 控制器、提供者、导出项 */ }) export class UserModule {}
问题场景
在服务中获取用户后遍历subscriptionHistory数组,判断操作类型后尝试读取details属性时触发错误:
export class SomeService { async someHandler(userId) { const user = await this.userService.getUserById(userId); // 查询正常 const action = user.subscriptionHistory.find((h) => { if (h.action === SubscriptionAction.Upgrade) { // 判断类型正常 console.log(h) // 打印结果包含'details'字段 const { details } = h as UpgradeAction // 强制类型转换 return details.oldVersion > 1; // 此处触发错误 } }); } }
错误信息:
Cannot read properties of undefined (reading 'oldVersion')
调试可见h为EmbeddedDocument类型,details字段存在但无法访问:
解决方案
1. 修正鉴别器注册方式
当前模块中单独注册BaseAction的方式不正确,应该直接在UserSchema的subscriptionHistory字段对应的Schema上添加鉴别器,而不是单独注册。
修改User模式的定义:
@Schema() export class User { @Prop({ required: true, type: [BaseAction] }) subscriptionHistory: BaseAction[]; } export const UserSchema = SchemaFactory.createForClass(User); // 给subscriptionHistory的嵌入式Schema添加鉴别器 const baseActionEmbeddedSchema = UserSchema.path('subscriptionHistory').schema; baseActionEmbeddedSchema.discriminator(SubscriptionAction.Upgrade, UpgradeActionSchema); // 其他鉴别器也按此方式添加 export type UserModel = Model<User>; export type UserDocument = HydratedDocument<User>;
然后模块中只需注册UserSchema,移除单独的BaseAction注册项:
@Module({ imports: [ MongooseModule.forFeature([ { name: User.name, schema: UserSchema }, ]) ], /* 控制器、提供者、导出项 */ }) export class UserModule {}
2. 使用类型守卫确保类型安全
避免直接强制类型转换,改用类型守卫来准确判断对象类型,同时让TypeScript正确推断字段:
// 定义类型守卫函数 function isUpgradeAction(action: BaseAction): action is UpgradeAction { return action.action === SubscriptionAction.Upgrade; } // 在服务中使用 export class SomeService { async someHandler(userId) { const user = await this.userService.getUserById(userId); const action = user.subscriptionHistory.find((h) => { if (isUpgradeAction(h)) { // 此处TypeScript会自动推断h为UpgradeAction,无需强制转换 return h.details.oldVersion > 1; } return false; }); } }
3. 转换为普通JavaScript对象访问
如果上述方法仍有问题,可以将Mongoose的HydratedDocument转换为普通JS对象,这样就能直接访问所有属性:
export class SomeService { async someHandler(userId) { const userDoc = await this.userService.getUserById(userId); const user = userDoc.toObject(); // 转换为普通对象 const action = user.subscriptionHistory.find((h) => { if (h.action === SubscriptionAction.Upgrade) { return h.details.oldVersion > 1; } return false; }); } }
内容的提问来源于stack exchange,提问作者KorbenDose
相关产品推荐
相关产品推荐

