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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 22:14:53