如何使用 Enum 类型的字段执行 Typesafe JSON?
Posted
技术标签:
【中文标题】如何使用 Enum 类型的字段执行 Typesafe JSON?【英文标题】:How to do Typesafe JSON with fields of Enum type? 【发布时间】:2020-06-03 03:49:28 【问题描述】:我有一些符合我定义的 TypeScript 接口的 JSON 文件。
在大多数情况下,当导入此类 JSON 文件并将它们分配给类型化变量时,TypeScript 能够自动推断类型签名(参见下面代码中的 behaves exactly as I want
)。但是,当类型包含 字符串枚举 时,它不再起作用(请参阅 DOES NOT behave
)。
以下是工作中的Minimal, Reproducible Example:
valid.json
"id": 3.14159
invalid.json
"id": "3.14159"
validEnum.json
"color": "red"
invalidEnum.json
"color": "chartreuse"
index.ts
import validJson from './valid.json'
import invalidJson from './invalid.json'
import validJsonEnum from './validEnum.json'
import invalidJsonEnum from './invalidEnum.json'
type ColorType = 'red' | 'blue' | 'yellow'
type IJsonType = "id": number
type IJsonTypeWithEnum = "color": ColorType
// behaves exactly as I want
const a: IJsonType = validJson // no error
const b: IJsonType = invalidJson // ERROR: Type 'string' is not assignable to type 'number'.
// DOES NOT behave as I want: SHOULD NOT error
const c: IJsonTypeWithEnum = validJsonEnum // ERROR: Type 'string' is not assignable to type 'ColorType'.
// DOES NOT behave as I want: error should be that "chartreuse" is not assignable to type 'ColorType'
const d: IJsonTypeWithEnum = invalidJsonEnum // ERROR: Type 'string' is not assignable to type 'ColorType'.
我可以使用type IJsonTypeWithEnum = "color": string
使错误消失,但这违背了目的。
是否有任何变通方法或编译器开关可以让 TypeScript 将 JSON 中的枚举值识别为字符串?还是 JSON 类型推断的 TypeScript 限制?
【问题讨论】:
如果你没有找到更好的选择 - 看看io-ts
。
如果您遵循How to create a Minimal, Reproducible Example 的 SO guildines,这个问题可以变得更容易理解和解决。我会尽快提交修改建议。
所以我提交的编辑中的 MRE 重现了您的错误消息,但我无法让肯定的情况起作用(“TypeScript 能够自动推断类型签名”)。您能否接受我的编辑,然后添加代码以显示不涉及枚举字符串时它是如何工作的?现在您可以看到 MRE 对这个问题的重要性。一旦获得可靠的 MRE,我会支持这个问题。
@Inigo 我已经检查了您的编辑。感谢您的效果,但它不正确。允许在最新版本的 TS 中导入 .json
文件,它将被转换为类型良好的对象。您的 MRE 的字符串模板应替换为对象文字以反映事实。仅供参考,我会改进您的编辑并应用它。
@Inigo 错误消息只是表面上相同,但原因完全不同。您对另一个答案 Conversion of type 'string' to type 'IJsonType' may be a mistake
发表评论,这是因为最初您使用字符串模板将 jsonData
声明为字符串,如果您从 .json
文件中导入它,则情况并非如此。
【参考方案1】:
尝试使用类型断言
const data: IJsonType = jsonData as IJsonType;
【讨论】:
这会产生错误Conversion of type 'string' to type 'IJsonType' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.(2352)
这不违背自动类型推断吗?这个想法是当 JSON 中的类型和数据不匹配时,让 TypeScript 产生编译时错误。
@vitaly-t 这两个答案都提供了有用的信息。我了解您想要存档的内容,但目前 TS 无法做到这一点,我怀疑它永远不会。
你看到一个字符串文字值"foo"
可以被推断为string
类型或'foo'
类型。它们都是有效的,但你必须选择一个。 TS 默认选择string
cus 这是常见的情况。如果你想要另一个选项,你必须使用as const
后缀告诉 TS 引擎你的意图。这在 .ts
文件中是可行的,就像其他答案所建议的那样。但是,在 .json
文件中,它将是无效的语法。我不确定一个 TS 插件是否可以提供这样的增强功能。所以在我看来,类型断言是你用例的唯一出路。【参考方案2】:
在这种情况下,Typescript 不会为您转换。您需要自己进行类型转换
type ColorType = 'red' | 'blue' | 'yellow';
type IJsonType = "color" : ColorType
let validJson =
"color": "red" as ColorType
// or
// "color": "red" as const
let invalidJson =
"color": "chartreuse"
const data1: IJsonType = validJson; // this MUST NOT error
const data2: IJsonType = invalidJson;
【讨论】:
实际上并没有解决问题:JSON 是.json
文件,您不能将类型注释放在 JSON 文件中。错误在于缺少正确的 MRE。我已经提交了一个修改,其中包含一个正在等待批准的修改。
@Inigo,Json 是一个文件,数据应该是运行时的东西。您正在尝试检查的是编译时的运行时错误?
这不是我的问题,但是如果您查看原始帖子,是的,这就是他想要的。这不是不合理的,因为在将 JSON 派生对象分配给 IJsonType
时,Typescript 可以静态捕获 string
与 number
不匹配。
是的,我正在寻找来自 JSON 文件的自动类型推断,而不是来自代码内 JSON。【参考方案3】:
目前在 Typescript 中是不可能的。 有一个未解决的问题/建议:https://github.com/microsoft/TypeScript/issues/32063(以及一些类似的)。
【讨论】:
谢谢,这确实是一个非常有用的链接。但除此之外,正如其他人所建议的那样,可以同时使用临时的const
解决方法。一个好的答案应该包括这两件事,IMO。因此,我很难接受哪个答案:)【参考方案4】:
最简单的方法是使用as const
,但如果你导入 json - 那么我不确定它是否可能。
还有amakhrov
指出当前的TS问题:https://github.com/microsoft/TypeScript/issues/32063
在问题得到解决/实现之前,您可以编写一个类型断言函数来验证类型,请查看下面的verifyColor
。它接受一个参数并断言它是IJsonTypeWithEnum
。
// helps to detect flexible objects
export const isObject = (value: unknown): value is [key in keyof any]: unknown =>
return typeof value === 'object' && value !== null;
// solves issues with for key of
export const objectKeys = <T extends object, K extends keyof T>(value: T): Array<K> =>
return <Array<K>>Object.keys(value);
;
// THAT'S THE ANSWER
// verifies colors
const verifyColor = (value: unknown): value is IJsonTypeWithEnum =>
if (!isObject(value))
return false;
for (const key of objectKeys(ColorType))
if (ColorType[key] === value.color)
return true;
return false;
;
const validJson: unknown = "id": 3.14159 ;
const invalidJson: unknown = "id": "3.14159" ;
const validJsonEnum: unknown = "color": "red" ;
const invalidJsonEnum: unknown = "color": "chartreuse" ;
enum ColorType
red = 'red',
blue = 'blue',
yellow = 'yellow',
type IJsonType = "id": number
type IJsonTypeWithEnum = "color": ColorType
// asserting type
if (verifyColor(validJsonEnum)) // add || 1 to check failure
const c: IJsonTypeWithEnum = validJsonEnum; // no error anymore
// asserting type
if (verifyColor(invalidJsonEnum)) // add || 1 to check failure
const d: IJsonTypeWithEnum = invalidJsonEnum // no error anymore
【讨论】:
Damien Plewa 已经给出了答案,几乎相同。加上来自 amakharov 的回答,带有正确的问题链接。一个好的答案应该包括这两件事;) 我没有看到带有类型断言的答案,可以在不推迟代码交付和等待新的 ts 版本的情况下解决问题 我不确定这是否适用于 JSON 文件。问题是关于从 JSON 文件中自动推断数据,而不是从内联 JSON 数据中。 Damien Plewa 使用类型转换。它不是类型安全的,也不是强制类型。在我的回答中,有一个类型安全检查可以保证正确的值。 已更新,我还删除了cast
东西,因为无论如何这不是解决方案,并添加了有关类型断言函数的更多信息。以上是关于如何使用 Enum 类型的字段执行 Typesafe JSON?的主要内容,如果未能解决你的问题,请参考以下文章