TypeScript中如何让泛型函数校验返回对象类型并抛出错误?
解决方案
核心问题分析
你的parseJsonField函数仅完成了Prisma.JsonValue到JSON对象的转换,没有任何结构校验逻辑:
- TypeScript泛型是编译时类型,运行时不存在
T的类型信息,无法自动校验结构 JSON.parse(JSON.stringify(jsonValue))只会生成普通JS对象,不会自动实例化ProgramTagsDTO类- 多余字段不会触发JSON解析错误,所以函数不会抛出异常
要实现预期的校验+实例化效果,需要补充运行时校验逻辑,同时完成DTO实例的创建。
方案1:针对DTO编写手动校验逻辑
给ProgramTagsDTO添加静态校验方法,在转换时校验结构并创建实例:
1. 修改ProgramTagsDTO类
export class ProgramTagsDTO { key: string; value: string; constructor(key: string, value: string) { this.key = key; this.value = value; } // 静态校验方法:检查对象是否符合DTO结构,无多余字段 static validate(obj: unknown): asserts obj is Omit<ProgramTagsDTO, 'constructor'> { if (typeof obj !== 'object' || obj === null) { throw new Error('Invalid ProgramTagsDTO: must be an object'); } const allowedKeys = new Set(['key', 'value']); const actualKeys = Object.keys(obj); // 检查是否有多余字段 const extraKeys = actualKeys.filter(key => !allowedKeys.has(key)); if (extraKeys.length > 0) { throw new Error(`Invalid ProgramTagsDTO: unexpected fields ${extraKeys.join(', ')}`); } // 检查必要字段是否存在且类型正确 if (typeof (obj as ProgramTagsDTO).key !== 'string') { throw new Error('Invalid ProgramTagsDTO: key must be a string'); } if (typeof (obj as ProgramTagsDTO).value !== 'string') { throw new Error('Invalid ProgramTagsDTO: value must be a string'); } } // 从普通对象创建DTO实例 static from(obj: unknown): ProgramTagsDTO { this.validate(obj); const { key, value } = obj as ProgramTagsDTO; return new ProgramTagsDTO(key, value); } }
2. 修改parseJsonField函数
parseJsonField<T>(jsonValue: Prisma.JsonValue, fromPlainObject: (obj: unknown) => T): T { // 先做序列化转换(处理Prisma.JsonValue的特殊类型,比如BigInt等) const parsed = JSON.parse(JSON.stringify(jsonValue)); // 如果是数组,逐个校验并转换 if (Array.isArray(parsed)) { return parsed.map(item => fromPlainObject(item)) as unknown as T; } // 单个对象的情况 return fromPlainObject(parsed); }
3. 修改测试代码
it('should throw error for extra fields', () => { const tags: Prisma.JsonValue = [ { key: 'key1', value: 'value1', wrongField: 'value', }, { key: 'key2', value: 'value2', }, ]; expect(() => { programmesService.parseJsonField<ProgramTagsDTO[]>(tags, ProgramTagsDTO.from); }).toThrowError('Invalid ProgramTagsDTO: unexpected fields wrongField'); }); it('should convert valid data correctly', () => { const tags: Prisma.JsonValue = [ { key: 'key1', value: 'value1' }, { key: 'key2', value: 'value2' }, ]; const result = programmesService.parseJsonField<ProgramTagsDTO[]>(tags, ProgramTagsDTO.from); const expectedResult = [ new ProgramTagsDTO('key1', 'value1'), new ProgramTagsDTO('key2', 'value2'), ]; expect(result).toEqual(expectedResult); });
方案2:使用类型校验库(推荐)
如果有大量DTO需要校验,手动写校验逻辑会很繁琐,可以用Zod这类库,实现编译时类型推导+运行时校验的统一:
1. 安装Zod
npm install zod
2. 用Zod定义DTO的Schema
import { z } from 'zod'; // 定义Schema,strict()模式拒绝多余字段 const ProgramTagsSchema = z.object({ key: z.string(), value: z.string(), }).strict(); // 自动推导TypeScript类型 type ProgramTagsDTOType = z.infer<typeof ProgramTagsSchema>; // 封装为类(可选,若不需要类实例可跳过) export class ProgramTagsDTO { key: string; value: string; constructor(data: ProgramTagsDTOType) { this.key = data.key; this.value = data.value; } static from(data: unknown): ProgramTagsDTO { const validated = ProgramTagsSchema.parse(data); return new ProgramTagsDTO(validated); } }
3. 复用parseJsonField函数
和方案1的函数完全一致,核心是传入校验转换函数即可。
4. 测试代码
it('should throw error for invalid data', () => { const tags: Prisma.JsonValue = [ { key: 'key1', value: 'value1', wrongField: 'value' }, ]; expect(() => { programmesService.parseJsonField<ProgramTagsDTO[]>(tags, ProgramTagsDTO.from); }).toThrowError(); });
关键说明
- 必须区分编译时类型和运行时逻辑:TypeScript的泛型无法直接用于运行时校验,必须补充运行时的校验规则
strict()模式(或手动检查多余字段)是实现"精确属性匹配"的关键,默认的宽松校验会忽略多余字段- 如果不需要DTO类实例,直接用Zod的
Schema.parse就能完成校验+类型转换,无需额外封装类
内容的提问来源于stack exchange,提问作者Kronos24
相关产品推荐
相关产品推荐

