如何使用 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 可以静态捕获 stringnumber 不匹配。 是的,我正在寻找来自 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?的主要内容,如果未能解决你的问题,请参考以下文章

如何在mysqlworkbench的表的某一字段类型是enum

慎使用sql的enum字段类型

使用 Enum 填充字段的通用工厂

mysql表的enum类型字段

sql enum类型

mysql怎么获取数据表字段enum类型的默认值