如何将含字符串枚举属性的JSON转换为TypeScript接口类型
问题背景
- 项目中使用 TypeScript 原生 string enum 实现后端到客户端的数据传输,受项目限制无法替换现有字符串枚举方案。
- 编写 mock 模拟数据时,mock JSON 文件中包含对应的枚举字符串值。在 tsconfig 中开启
resolveJsonModule配置后,通过import testJson from './test.json'方式导入 mock JSON 文件,TypeScript 会基于 JSON 的静态结构推导类型,此时枚举属性会被推导为普通string类型,而非对应的枚举类型。 - 尝试将导入的 JSON 对象赋值为对应接口类型时,会抛出类型错误:
Type 'string' is not assignable to type 'testEnum'.,如果使用as any类型断言绕过校验,会完全丢失类型安全性。
复现示例
const testJSON = { name: 'hello World', enumProp: "MYTESTENUM_2", }; enum testEnum { MYTESTENUM = "MYTESTENUM", MYTESTENUM_2 = "MYTESTENUM_2", } interface testinterface { name: string; enumProp: testEnum; } // const castJsonFile: testinterface = testJSON; // 无法编译,字符串枚举类型无法完成转换 const castJsonFile: testinterface = testJSON as any; // 通过any断言可通过编译,但丢失全部类型安全性 const appDiv: HTMLElement = document.getElementById('app'); appDiv.innerHTML = (castJsonFile.enumProp === testEnum.MYTESTENUM_2) as any as string;
解决方案
出现该问题的核心原因是 TypeScript 导入 JSON 模块时,默认会将字符串值推导为宽泛的 string 类型,不会自动匹配同值的字符串枚举。在不替换现有 string enum 方案的前提下,可根据场景选择以下三种实现方式:
方案1:as const 收窄类型(轻量化实现,无额外依赖)
导入 JSON 时增加 as const 断言,将字符串值收窄为精确字面量类型,再对枚举属性做定向类型断言,TypeScript 会自动校验值是否属于枚举范围,不会像 as any 一样跳过所有校验:
// 导入时加as const收窄类型 import testJson from './test.json' as const; const castJsonFile: testinterface = { name: testJson.name, // 如果枚举值写错,TS会直接抛出类型错误,保留类型安全 enumProp: testJson.enumProp as testEnum };
- 优点:零额外成本,代码改动量极小
- 缺点:每个导入位置需要手动添加断言
方案2:通用类型守卫(双重校验,推荐生产环境使用)
编写通用字符串枚举校验工具,导入 JSON 后先经过校验逻辑转换,同时实现编译时类型匹配和运行时值校验,可拦截 mock 数据写错枚举值的问题:
/** * 通用字符串枚举值校验函数 */ function isEnumValue<T extends string>(enumObj: Record<string, T>, value: unknown): value is T { return Object.values(enumObj).includes(value as T); } /** * 接口数据解析函数 */ function parseTestInterface(raw: unknown): testinterface { if (typeof raw !== 'object' || raw === null) { throw new Error('mock数据格式错误'); } const data = raw as Record<string, unknown>; if (typeof data.name !== 'string') { throw new Error('name字段类型错误'); } if (!isEnumValue(testEnum, data.enumProp)) { throw new Error(`enumProp字段值错误,有效值为${Object.values(testEnum).join(',')}`); } return { name: data.name, enumProp: data.enumProp }; } // 导入后解析,得到完全类型安全的对象 import testJson from './test.json'; const castJsonFile = parseTestInterface(testJson);
- 优点:编译时+运行时双重校验,错误提示明确,完全保留类型安全性
- 缺点:需要编写少量校验逻辑
方案3:JSON模块类型声明(固定结构mock适用,导入即得正确类型)
在 mock JSON 文件同目录下创建同名 .d.ts 类型声明文件,手动声明该JSON模块的对应类型,后续导入时 TypeScript 会自动识别为目标接口类型,无需额外断言:
以 test.json 为例,同目录下创建 test.json.d.ts:
declare module './test.json' { const value: testinterface; export default value; }
- 优点:业务代码导入时直接得到正确类型,无需额外写断言逻辑
- 缺点:每个JSON文件需要维护对应类型声明,JSON结构变更时需要同步更新声明内容
注意:禁止直接对未收窄类型的字符串使用
as testEnum断言,此时 TypeScript 不会校验值是否属于枚举范围,和as any一样存在类型安全隐患,例如enumProp: 'invalid_value' as testEnum不会触发TS报错。
内容的提问来源于stack exchange,提问作者Stefan
相关产品推荐
相关产品推荐

