Typescript JSON模块深层类型信息



考虑文件data.json:

{
"au": {
"name": "Australia",
"market_id": "111172",
"language_code": "en_AU"
},
"br": {
"name": "Brazil",
"market_id": "526970",
"language_code": "pt_BR"
},
"ch": {
"name": "China",
"market_id": "3240",
"language_code": "zh_CN"
},
"de": {
"name": "Germany",
"market_id": "4",
"language_code": "de_DE"
}
...many more
}

在Typescript中,我们可以将该文件导入为json模块("resolveJsonModule": true中的tsconfig.json):

import lookup from './data.json'

导入的模块有类型信息:

type LookupType = typeof lookup
LookupType = {
au: {
name: string;
market_id: string;
language_code: string;
};
br: {
name: string;
market_id: string;
language_code: string;
};
ch: {
name: string;
market_id: string;
language_code: string;
};
de: {
name: string;
market_id: string;
language_code: string;
};
...
}
有了这些类型信息,我们可以这样做:
type CountryCodes = keyof LookupType
CountryCodes = "au" | "br" | "ch" | "de" | ...

然而,我想做的是提取一个类型,该类型代表json中给定嵌套字段的所有可能值。

例如:

type MarketIds = LookupType[keyof LookupType]['market_id']
MarketIds = string // I'd like "111172" | "526970" | "3240" | "4" ...

问题是Typescript将LookupType[keyof LookupType]视为类型擦除的联合:

LookupType[keyof LookupType] = {
name: string;
market_id: string;
language_code: string;
} | {
name: string;
market_id: string;
language_code: string;
} | {
name: string;
market_id: string;
language_code: string;
} | {
name: string;
market_id: string;
language_code: string;
} ...

我知道这可能只是Typescript的一个硬性限制。-可以理解的是,考虑到深度嵌套的类型json数据会炸毁编译器,我猜。

然而,由于我没有在文档中找到任何关于json模块只支持一层类型信息的明确信息,我希望可能有一些方法可以实现上面概述的目标。

当将初始化值赋给没有类型注释的变量时,编译器会使用一些启发式方法来推断该变量的类型。对于对象字面量,编译器将属性推断为像"au""name"这样的字面量类型,因此它记住并强制键名保持不变。但是它更广泛地推断属性,就像stringnumber一样,所以它通常不会记住或强制值保持最初的值。毕竟,对象属性的值经常会改变。

如果你这样做:

const lookup = {
"au": {
"name": "Australia",
"market_id": "111172",
"language_code": "en_AU"
},
"br": {
"name": "Brazil",
"market_id": "526970",
"language_code": "pt_BR"
}
}

你得到这个:

/* const lookup: {
au: {
name: string;
market_id: string;
language_code: string;
};
br: {
name: string;
market_id: string;
language_code: string;
};
} */

当然,这并不总是我们想要的。有时人们使用的初始化值实际上根本不应该改变;在这种情况下,如果编译器能够尽可能多地记住初始值,并且不允许任何人在之后更改它,那就太好了。

如果你在TypeScript中编写这样的初始化器,你可以使用const断言来达到这个效果:

const lookup = {
"au": {
"name": "Australia",
"market_id": "111172",
"language_code": "en_AU"
},
"br": {
"name": "Brazil",
"market_id": "526970",
"language_code": "pt_BR"
}
} as const;

产生如下类型:

/* const lookup: {
readonly au: {
readonly name: "Australia";
readonly market_id: "111172";
readonly language_code: "en_AU";
};
readonly br: {
readonly name: "Brazil";
readonly market_id: "526970";
readonly language_code: "pt_BR";
};
} */

这里,每个属性现在都被认为是readonly,每个值都被认为是字符串文字类型。现在编译器对lookup有了相当多的了解,从这里你可以使用typeof lookup来提取所有你想要的字符串字面量的union -of-string-literal。


不幸的是,const断言(从TypeScript 4.4开始)只有当有问题的初始化器在TypeScript代码中时才可用。

--resolveJsonModule编译器标志允许您将importJSON文件视为模块,但是没有机会将as const放在任何地方。因此,这样导入的JSON文件的类型对应于期望属性更改的常规启发式。

在microsoft/TypeScript#32063中有一个特性请求请求支持importas const。它目前是开放的,并标记为"等待更多的反馈",所以如果你想看到这个实现,你可能会想去那个问题,给它一个👍并描述你的用例和为什么它是引人注目的。

除非它被实现,否则我不确定是否有一个很好的解决方案适合你。你可以添加一个额外的构建步骤,从JSON文件中生成一个TypeScript文件或一个声明文件,并从中导入类型;请参阅本期的评论和其他相关评论。这类似于说"忘记JSON,只使用TypeScript",所以这并不理想。但这总比什么都没有好。🤷‍♂️

Playground链接到代码

最新更新