TypeScript如何根据传入对象类型选择对应属性实现通用解析函数
TypeScript 联合类型对象通用解析方案
问题描述
现有两种不同结构的话题对象类型,分别对应不同数据源的字段命名规则,需要实现通用的parseTopic函数,自动识别传入对象的结构、提取对应字段值完成解析。直接访问联合类型的独有属性时,TypeScript会抛出类型错误,提示属性不存在于联合类型的另一成员上。
原始问题代码如下:
enum FirstSourceTopicAliases { Topic = 'topic', TopicText = 'topic_text', TopicLink = 'topic_link', } enum SecondSourceTopicAliases { Topic = 'title', TopicText = 'body', TopicLink = 'link', } interface FirstSourceTopic { [FirstSourceTopicAliases.Topic]: string, [FirstSourceTopicAliases.TopicText]: string, [FirstSourceTopicAliases.TopicLink]: string, } interface SecondSourceTopic { [SecondSourceTopicAliases.Topic]: string, [SecondSourceTopicAliases.TopicText]: string, [SecondSourceTopicAliases.TopicLink]: string, } type Topic = FirstSourceTopic | SecondSourceTopic const parseTopic = (topic: Topic) => { const parsedTopic = { topic: 'parsed' + topic.topic // 或 topic.title,直接写会报TS错误 topicText: 'parsed' + topic.topic_text // 或 topic.body,直接写会报TS错误 topicLink: 'parsed' + topic.topic_link, // 或 topic.link,直接写会报TS错误 } return parsedTopic } const firstSourceTopic: FirstSourceTopic = { topic: 'some topic', topic_text: 'some topic text', topic_link: 'some topic link', } const secondSourceTopic: SecondSourceTopic = { title: 'some topic', body: 'some topic text', link: 'some topic link', } const parsedTopic = parseTopic(firstSourceTopic) console.log(parsedTopic)
实现方案
核心思路是统一维护语义字段和各数据源别名的映射关系,通过in操作符做类型守卫自动识别当前对象存在的字段,不需要零散硬编码类型判断,后续扩展新的数据源也只需要修改映射配置,不需要改动核心解析逻辑。
改造后的完整代码:
enum FirstSourceTopicAliases { Topic = 'topic', TopicText = 'topic_text', TopicLink = 'topic_link', } enum SecondSourceTopicAliases { Topic = 'title', TopicText = 'body', TopicLink = 'link', } interface FirstSourceTopic { [FirstSourceTopicAliases.Topic]: string, [FirstSourceTopicAliases.TopicText]: string, [FirstSourceTopicAliases.TopicLink]: string, } interface SecondSourceTopic { [SecondSourceTopicAliases.Topic]: string, [SecondSourceTopicAliases.TopicText]: string, [SecondSourceTopicAliases.TopicLink]: string, } type Topic = FirstSourceTopic | SecondSourceTopic // 统一维护语义字段对应的所有数据源别名 const FIELD_ALIAS_MAP = { topic: [FirstSourceTopicAliases.Topic, SecondSourceTopicAliases.Topic], topicText: [FirstSourceTopicAliases.TopicText, SecondSourceTopicAliases.TopicText], topicLink: [FirstSourceTopicAliases.TopicLink, SecondSourceTopicAliases.TopicLink] } as const; /** * 通用字段取值函数:自动匹配当前topic对象存在的别名字段取值 */ const getFieldValue = (topic: Topic, semanticField: keyof typeof FIELD_ALIAS_MAP): string => { const aliases = FIELD_ALIAS_MAP[semanticField]; for (const alias of aliases) { // in 操作符是TS原生支持的类型守卫,可自动缩窄对象类型 if (alias in topic) { return topic[alias]; } } throw new Error(`Invalid topic structure: missing required field ${semanticField}`); } const parseTopic = (topic: Topic) => { return { topic: 'parsed ' + getFieldValue(topic, 'topic'), topicText: 'parsed ' + getFieldValue(topic, 'topicText'), topicLink: 'parsed ' + getFieldValue(topic, 'topicLink') } } // 测试用例 const firstSourceTopic: FirstSourceTopic = { topic: 'some topic', topic_text: 'some topic text', topic_link: 'some topic link', } const secondSourceTopic: SecondSourceTopic = { title: 'some topic', body: 'some topic text', link: 'some topic link', } console.log(parseTopic(firstSourceTopic)) // 输出:{ topic: 'parsed some topic', topicText: 'parsed some topic text', topicLink: 'parsed some topic link' } console.log(parseTopic(secondSourceTopic)) // 输出:{ topic: 'parsed some topic', topicText: 'parsed some topic text', topicLink: 'parsed some topic link' }
方案优势
- 完全通过TS类型检查,不会出现联合类型属性访问报错
- 解析逻辑和字段映射解耦,后续新增其他数据源的Topic类型,只需要新增对应别名枚举、补充映射配置即可,不需要修改
parseTopic核心逻辑 - 容错性强,遇到结构不符合要求的对象会抛出明确的错误信息,方便排查问题
内容的提问来源于stack exchange,提问作者barciewicz
相关产品推荐
相关产品推荐

