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

如何在NestJS的Mongoose Schema中创建泛型类型

如何在NestJS的Mongoose Schema中创建泛型类型

兄弟,我懂你想做什么——用TypeScript泛型给NestJS的Mongoose Schema加上强类型约束,让MongoDB的数据结构严丝合缝,同时还能配合Joi这类工具做验证对吧?我之前也折腾过类似的需求,下面给你一步步捋清楚怎么实现:

第一步:完善类型枚举与映射

首先先把你定义的AvailableTypes枚举补全,同时创建一个TypeScript类型映射,把枚举值对应到实际的TS类型,这样泛型才能准确约束:

import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from 'mongoose';

export enum AvailableTypes {
  STRING = 'string',
  NUMBER = 'number',
  BOOLEAN = 'boolean',
  DATE = 'date',
  ARRAY = 'array',
}

// 把枚举值映射到对应TypeScript类型,方便泛型约束
type TypeMapping = {
  [AvailableTypes.STRING]: string;
  [AvailableTypes.NUMBER]: number;
  [AvailableTypes.BOOLEAN]: boolean;
  [AvailableTypes.DATE]: Date;
  [AvailableTypes.ARRAY]: any[]; // 后面可以优化成更严格的数组类型
};

第二步:创建泛型Schema类

接下来定义泛型的Schema类,开启Mongoose的strict: true模式(这很重要,能确保MongoDB只存储Schema定义的字段,完全符合你的“严格约束数据”需求),然后通过泛型来动态约束字段类型:

// 泛型文档类型,继承Mongoose的Document,方便后续CRUD操作
export type GenericDataDocument<T extends keyof TypeMapping> = GenericData<T> & Document;

@Schema({ strict: true })
export class GenericData<T extends keyof TypeMapping> {
  @Prop({
    required: true,
    // 用函数动态返回Mongoose类型,适配泛型参数
    type: function(this: GenericData<T>) {
      const dataType = T as unknown as AvailableTypes;
      switch (dataType) {
        case AvailableTypes.STRING:
          return String;
        case AvailableTypes.NUMBER:
          return Number;
        case AvailableTypes.BOOLEAN:
          return Boolean;
        case AvailableTypes.DATE:
          return Date;
        case AvailableTypes.ARRAY:
          return Array;
        default:
          throw new Error(`不支持的数据类型:${dataType}`);
      }
    },
  })
  value: TypeMapping[T];

  // 存一份类型标识,双重保障数据类型一致性
  @Prop({ required: true, enum: AvailableTypes })
  type: T;
}

// 创建泛型Schema工厂
export const GenericDataSchema = SchemaFactory.createForClass(GenericData);

第三步:使用泛型Schema

现在你可以根据业务需要创建特定类型的Schema,注册到NestJS的Mongoose模块里:

import { MongooseModule } from '@nestjs/mongoose';

// 比如创建字符串类型的专属Schema
const StringDataSchema = SchemaFactory.createForClass(GenericData<AvailableTypes.STRING>);
// 再创建数字类型的专属Schema
const NumberDataSchema = SchemaFactory.createForClass(GenericData<AvailableTypes.NUMBER>);

@Module({
  imports: [
    MongooseModule.forFeature([
      { name: 'StringData', schema: StringDataSchema },
      { name: 'NumberData', schema: NumberDataSchema },
    ]),
  ],
})
export class DataModule {}

进阶:更严格的数组类型约束

如果你想让数组类型更精准(比如指定数组元素是字符串还是数字),可以调整泛型类,支持第二个泛型参数来约束数组元素:

// 升级类型映射,支持数组元素类型的泛型约束
type TypeMapping<ArrayItem = any> = {
  [AvailableTypes.STRING]: string;
  [AvailableTypes.NUMBER]: number;
  [AvailableTypes.BOOLEAN]: boolean;
  [AvailableTypes.DATE]: Date;
  [AvailableTypes.ARRAY]: ArrayItem[];
};

// 泛型文档类型,新增数组元素类型的泛型参数
export type GenericDataDocument<T extends keyof TypeMapping, U = any> = GenericData<T, U> & Document;

@Schema({ strict: true })
export class GenericData<T extends keyof TypeMapping, U = any> {
  @Prop({
    required: true,
    type: function(this: GenericData<T, U>) {
      const dataType = T as unknown as AvailableTypes;
      if (dataType === AvailableTypes.ARRAY) {
        // 动态返回数组元素的Mongoose类型
        return [this.getArrayElementType(U)];
      }
      switch (dataType) {
        case AvailableTypes.STRING:
          return String;
        case AvailableTypes.NUMBER:
          return Number;
        case AvailableTypes.BOOLEAN:
          return Boolean;
        case AvailableTypes.DATE:
          return Date;
        default:
          throw new Error(`不支持的数据类型:${dataType}`);
      }
    },
  })
  value: TypeMapping<U>[T];

  @Prop({ required: true, enum: AvailableTypes })
  type: T;

  // 辅助方法:根据元素类型返回Mongoose对应的类型
  private getArrayElementType(type: any) {
    if (type === String) return String;
    if (type === Number) return Number;
    if (type === Boolean) return Boolean;
    if (type === Date) return Date;
    return type; // 支持自定义嵌套类型
  }
}

现在你可以创建比如“字符串数组”类型的Schema:

const StringArrayDataSchema = SchemaFactory.createForClass(GenericData<AvailableTypes.ARRAY, string>);

配合Joi验证的小技巧

为了和MongoDB的Schema形成双重验证,你可以给DTO也加上泛型约束:

import * as Joi from 'joi';

export class GenericDataDto<T extends keyof TypeMapping> {
  value: TypeMapping[T];
  type: T;
}

// 动态生成Joi验证规则
export const getGenericDataJoiSchema = <T extends keyof TypeMapping>(type: T) =>
  Joi.object({
    value: getJoiValidationType(type).required(),
    type: Joi.string().valid(type).required(),
  });

function getJoiValidationType(type: keyof TypeMapping) {
  switch (type) {
    case AvailableTypes.STRING:
      return Joi.string();
    case AvailableTypes.NUMBER:
      return Joi.number();
    case AvailableTypes.BOOLEAN:
      return Joi.boolean();
    case AvailableTypes.DATE:
      return Joi.date();
    case AvailableTypes.ARRAY:
      return Joi.array();
    default:
      throw new Error(`不支持的验证类型:${type}`);
  }
}

这样在Controller里就能用这个DTO和Joi规则做请求验证,和数据库的Schema完全对应上,双重保障数据合法性。

备注:内容来源于stack exchange,提问作者Krigan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 16:53:01