如何在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
相关产品推荐
相关产品推荐

