TypeScript中JSON解析、导入与类型转换的差异及相关疑问
场景1:使用JSON.parse转换
app.ts
const data = [{ startTs: '1', endTs: '2020-10-16T14:00:52.224Z', averageCount: 3.504, entrances: 0 }, { startTs: '1', endTs: '2020-10-16T14:00:52.224Z', averageCount: 3.504, entrances: 0 }]; const hr: HistoricalRecord[] = JSON.parse(JSON.stringify(data)) as HistoricalRecord[];
场景2:直接导入JSON文件
dataExx.json
[ { startTs: '1', endTs: '2020-10-16T14:00:52.224Z', averageCount: 3.504, entrances: 0 }, { startTs: '1', endTs: '2020-10-16T14:00:52.224Z', averageCount: 3.504, entrances: 0 } ]
app.ts
import jsondata from '../../testData/dataExx.json'; const hr: HistoricalRecord[] = jsondata as HistoricalRecord[];
- JSON.parse返回
any类型,因此需要断言为HistoricalRecord[],那直接导入的JSON是什么类型? - 当前HistoricalRecord是类,若改为interface会有区别吗?
- 是否可以认为TypeScript处理JSON时,无论用类还是接口都没有即时类型安全?
- 修改JSON文件中第二个条目averageCount为字符串后,断言为HistoricalRecord[]时编译器未报错,为何?
- 单个JSON对象的averageCount为字符串时编译器报错,但数组时不报错,原因是什么?
HistoricalRecord.ts
export class HistoricalRecord { startTs: string; endTs: string; averageCount: number; entrances: number; constructor(startTs: string, endTs: string, averageCount: number, entrances: number) { this.startTs = startTs; this.endTs = endTs; this.averageCount = averageCount; this.entrances = entrances; } }
1. 直接导入的JSON是什么类型?
TypeScript会根据JSON文件的内容自动推断出具体的结构类型,而非any。比如你导入的数组,TS会推断它是包含{ startTs: string; endTs: string; averageCount: number; entrances: number }元素的数组类型。不过这个推断是静态的,仅在编译时基于导入文件的内容生成,后续JSON文件若被修改,除非重新编译,否则类型不会自动更新。
2. 改为interface会有区别吗?
有明显区别:
- 接口(interface):仅为类型层面的定义,编译后会被完全移除,不存在于运行时。JSON本身就是普通JS对象,用接口断言时,TS仅在编译时做结构校验,运行时不会有实例化操作,也不会验证对象是否符合接口结构(除非你手动编写校验逻辑)。
- 类(class):编译后会保留为JS构造函数,是运行时存在的实体。如果直接把JSON对象断言为类实例,虽然编译时通过,但运行时该对象并未经过类的构造函数初始化,也不具备类可能拥有的方法(若有的话),本质还是普通对象,并非类的实例。
简言之,用接口更适合描述JSON数据的结构,因为JSON本来就是纯数据对象;用类的话,除非你手动将JSON数据转换为类实例(比如通过new HistoricalRecord(...)),否则断言只是编译时的“声明”,运行时并不真的是类实例。
3. 是否可以认为TypeScript处理JSON时,无论用类还是接口都没有即时类型安全?
可以这么说,但需补充:
TS的类型检查是编译时的,而JSON数据可能在运行时发生变化(比如后端返回格式变更、本地JSON文件被修改)。无论是用接口还是类,断言操作都会跳过TS的严格类型检查,相当于你告诉编译器“我确定这个数据符合类型”,但编译器不会在运行时验证数据真实性。
如果想要真正的类型安全,需要在运行时手动校验JSON数据结构(比如使用Zod、Yup这类库,或自行编写校验函数),确认数据符合预期后再断言或转换。
4. 修改JSON文件中第二个条目averageCount为字符串后,断言为HistoricalRecord[]时编译器未报错,为何?
因为TS的类型断言是你强制向编译器声明类型,编译器会信任你的判断,不再做严格的结构校验。当你使用as HistoricalRecord[]时,相当于绕过了TS的类型检查,即便JSON数据结构与HistoricalRecord不匹配,编译器也不会报错。
另外,若未开启严格模式(strict: true),TS的类型检查会更宽松,对于数组元素的类型不匹配可能不会主动检测。
5. 单个JSON对象的averageCount为字符串时编译器报错,但数组时不报错,原因是什么?
这和TS的类型推断及检查机制有关:
- 导入单个JSON对象时,TS会精确推断每个属性的类型,比如
averageCount会被推断为字符串类型。此时直接断言为HistoricalRecord(其中averageCount是number类型),在严格模式下,TS会检测到类型不兼容,因为单个对象的属性类型与目标类型直接冲突,编译器会抛出错误。 - 导入数组时,若数组中存在两种不同类型的
averageCount(一个number,一个string),TS会推断averageCount的类型为string | number。此时断言数组为HistoricalRecord[](其中averageCount是number类型),在非严格模式下,TS会允许这种断言,因为联合类型string | number与number存在一定兼容性(虽不严格);若开启严格模式,可能也会报错,但如果使用as any as HistoricalRecord[]这类双重断言,则会绕过检查。
总结来说,数组元素的类型推断会合并多个元素的属性类型,形成更宽松的联合类型,此时断言为更严格的类型时,非严格模式下编译器不会报错;而单个对象的类型推断是精确的,断言为不兼容类型时编译器会直接报错。
内容的提问来源于stack exchange,提问作者cien

