TypeScript:如何拓宽JSONValue类型以兼容现有接口?
如何拓宽TypeScript的JSONValue类型以支持接口传递
背景
这是两个TypeScript问题的延伸:先是探讨与JSON类型之间的类型转换,再延伸到向期望JSON类型的函数传递接口的问题,核心都围绕标准JSONValue类型展开:
type JSONValue = | string | number | boolean | null | JSONValue[] | {[key: string]: JSONValue}
问题重现
此前结论表明,无法直接将interface传递给期望JSONValue的函数,如下代码会报错:
interface Foo { name: 'FOO', fooProp: string } const bar = (foo: Foo) => { return foo } const wrap = <T extends JSONValue[]>( fn: (...args: T) => JSONValue, ...args: T ) => { return fn(...args); } wrap(bar, { name: 'FOO', fooProp: 'hello'});
报错原因是TypeScript不认为interface Foo可以赋值给JSONValue——尽管从结构上看Foo完全符合JSON规范,这是因为TypeScript将具名对象类型(如接口)和索引签名对象类型({[key: string]: JSONValue})视为不兼容的类型。
解决方案:拓宽JSONValue类型
要让JSONValue兼容所有符合JSON结构的接口,我们需要修改JSONValue的定义,让它不仅包含索引签名对象,还能接受所有属性值均为JSONValue的具名对象类型。
基础拓宽方案
修改后的JSONValue定义如下:
type JSONValue = | string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue } | { [K in keyof any]: JSONValue };
新增的{ [K in keyof any]: JSONValue }分支覆盖了所有具名对象类型(包括接口),只要这些对象的所有属性值都属于JSONValue类型。这样TypeScript就会将符合结构的接口视为JSONValue的子类型,从而允许将接口传递给期望JSONValue的函数。
严格约束方案
如果需要更严格地限制只接受符合JSON规范的对象(排除Date、Map等非JSON对象),可以结合递归条件类型进一步约束:
type IsJSONCompatible<T> = T extends string | number | boolean | null ? true : T extends Array<infer U> ? IsJSONCompatible<U> : T extends { [key: string]: infer V } ? IsJSONCompatible<V> : false; type JSONValue = | string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue } | (T extends object ? IsJSONCompatible<T> extends true ? T : never : never);
这种方式会自动过滤掉属性值不符合JSON规范的对象,同时兼容所有结构合法的接口。
内容的提问来源于stack exchange,提问作者theahura
相关产品推荐
相关产品推荐

