如何基于属性存在判别TypeScript类型?无需强制使用type字段
问题:TypeScript/JavaScript作者列表建模困境
我是TypeScript(及JavaScript)新手,在建模作者列表时遇到以下需求:
- 作者对象要么包含个人姓名属性(
family/given),要么仅包含name属性(用于机构类主体) - 允许可选的
location属性(针对出版商、机构类作者) - 能通过
typescript-json-schema自动导出JSON Schema,加载JSON后可正确识别类型以调用关联方法
目前我通过class-transformer的判别器实现:
@Type(() => Agent, { discriminator: { property: 'type', subTypes: [ { value: Organization, name: 'organization' }, { value: Person, name: 'person' }, ], }, }) author?: (Organization|Person)[];
该方案能正常运行,示例结果:
Reference { id: "un", type: "article", author: [ Organization { name: "United Nations" } ], title: "The Title", issued: "2020" }
但局限性明显:必须在JSON输入中添加type字段用于类型判别,而我希望通过是否存在family/given属性自动判断类型,无需额外的type字段。请问有更优替代方案吗?
替代方案:基于属性存在性的自定义类型转换
可以通过class-transformer的Transform装饰器结合自定义逻辑,实现无需type字段的类型判别,同时兼容typescript-json-schema的导出需求。
1. 定义基础类与子类
先明确Person、Organization和基础Agent类的结构,结合class-validator做基础校验:
import { Transform, plainToInstance } from 'class-transformer'; import { IsOptional, IsString, ValidateNested } from 'class-validator'; // 基础Agent类,包含通用属性 class Agent { @IsOptional() @IsString() location?: string; } // 个人作者类,继承Agent并添加姓名属性 class Person extends Agent { @IsString() family: string; @IsString() given: string; } // 机构作者类,继承Agent并添加名称属性 class Organization extends Agent { @IsString() name: string; }
2. 实现自定义类型转换逻辑
在Reference类的author字段上,用Transform装饰器替代原有判别器,根据属性存在性自动判断并转换类型:
class Reference { @IsString() id: string; @IsString() type: string; @IsOptional() @ValidateNested({ each: true }) @Transform(({ value }) => { if (!value) return value; // 处理单作者或多作者列表 const items = Array.isArray(value) ? value : [value]; return items.map(item => { // 存在family和given字段则转为Person实例 if ('family' in item && 'given' in item) { return plainToInstance(Person, item); } // 存在name字段则转为Organization实例 else if ('name' in item) { return plainToInstance(Organization, item); } throw new Error(`Invalid author structure: ${JSON.stringify(item)}`); }); }, { toClassOnly: true }) author?: (Person | Organization)[]; @IsString() title: string; @IsString() issued: string; }
3. 兼容JSON Schema导出
typescript-json-schema会自动识别Person | Organization联合类型,生成包含oneOf规则的JSON Schema,符合你的导出需求。只需确保字段上显式声明联合类型即可,无需额外配置。
验证效果
输入不含type字段的JSON数据:
{ "id": "un", "type": "article", "author": [ { "name": "United Nations", "location": "New York" }, { "family": "Doe", "given": "John" } ], "title": "The Title", "issued": "2020" }
通过plainToInstance(Reference, jsonData)转换后,会得到正确的实例:
Reference { id: "un", type: "article", author: [ Organization { name: "United Nations", location: "New York" }, Person { family: "Doe", given: "John", location: undefined } ], title: "The Title", issued: "2020" }
注意事项
- 确保输入数据结构唯一:不要同时给一个作者对象设置
name和family/given字段,否则会优先匹配Person类型 - 若需要更严谨的校验,可以添加自定义验证器,在转换前校验每个作者对象的结构合法性
- 如果使用
class-validator做全局校验,记得开启ValidateNested确保子类属性也被校验
内容的提问来源于stack exchange,提问作者Bruce D'Arcus
相关产品推荐
相关产品推荐

