You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

访问数组中嵌入式鉴别器属性时触发错误

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 12:22:18