【问题标题】:How to do Typesafe JSON with fields of Enum type?如何使用 Enum 类型的字段执行 Typesafe JSON?
【发布时间】: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 文件中导入它,则情况并非如此。

标签: json typescript enums


【解决方案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 东西,因为无论如何这不是解决方案,并添加了有关类型断言函数的更多信息。
猜你喜欢
  • 1970-01-01
  • 2015-10-26
  • 1970-01-01
  • 1970-01-01
  • 2019-07-06
  • 1970-01-01
  • 2019-08-08
  • 2020-03-18
  • 1970-01-01
相关资源
最近更新 更多