我在类型记录中使用一个通用的JSON类型,这是来自这里的建议
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| {[key: string]: JSONValue}我希望能够从与JSON类型匹配的接口类型中转换到JSON类型。例如:
interface Foo {
name: 'FOO',
fooProp: string
}
interface Bar {
name: 'BAR',
barProp: number;
}
const genericCall = (data: {[key: string]: JSONValue}): Foo | Bar | null => {
if ('name' in data && data['name'] === 'FOO')
return data as Foo;
else if ('name' in data && data['name'] === 'BAR')
return data as Bar;
return null;
}此操作当前失败,因为类型记录看不到接口如何与JSONValue相同类型:
Conversion of type '{ [key: string]: JSONValue; }' to type 'Foo' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Property 'name' is missing in type '{ [key: string]: JSONValue; }' but required in type 'Foo'.但是从分析上来说,我们当然知道这是可以的,因为我们认识到在运行时类型Foo和Bar是JSON兼容的。我如何告诉打字稿,这是一个好的演员?
ETA:我可以按照错误信息,先转换到未知,但我不想这么做--如果TS真的理解了差异,那就更好了,我想知道这是否可能。
发布于 2022-08-10 03:01:48
这里的问题是编译器没有使用check if ('name' in data && data['name'] === 'FOO')从其原始类型的窄中提取data的类型。类型{[key: string]: JSONValue}不是友联市,目前操作员检查仅缩小联合类型的值。微软/打字稿#21732有一个开放的特性请求来进行这样的缩小,但目前它并不是语言的一部分。
这意味着检查后data保持为{[key: string]: JSONValue}类型。然后,当您试图通过断言确认data是Foo类型时,编译器警告您您可能犯了错误,因为它没有看到Foo和{[key: string]: JSONValue}是关系足够密切的类型。
如果您确信您所做的是一个很好的检查,则始终可以使用编译器建议并对与Foo和{[key: string]: JSONValue} (例如unknown )相关的中间类型进行断言。
return data as unknown as Foo; // okay如果这与您有关,那么您可以编写您自己的用户定义类型保护功能,它执行您期望从if ('name' in data && data['name'] === 'FOO')中缩小的范围。本质上,如果该检查通过,那么我们就知道data是{name: 'FOO'}类型的,它与类型断言的Foo有足够的关联。下面是一个可能的类型保护功能:
function hasKeyVal<K extends PropertyKey, V extends string | number |
boolean | null | undefined | bigint>(
obj: any, k: K, v: V): obj is { [P in K]: V } {
return obj && obj[k] === v;
}因此,您不必编写if ('name' in data && data['name'] === 'FOO'),而是编写if (hasKeyVal(data, 'name', 'FOO'))。返回类型obj is {[P in K]: V}意味着如果函数返回true,编译器应该将obj类型缩小到具有键类型为K且值为V类型的属性的对象。让我们来测试一下:
const genericCall = (data: { [key: string]: JSONValue }): Foo | Bar | null => {
if (hasKeyVal(data, 'name', 'FOO'))
return data as Foo; // okay, data is now {name: 'FOO'} which is related to Foo
else if (hasKeyVal(data, 'name', 'BAR'))
return data as Bar; // okay, data is now {name: 'BAR'} which is related to Bar
return null;
}现在起作用了。hasKeyVal()检查将data缩小到具有正确类型的name属性的对象,这与Foo或Bar有足够的关联,类型断言才能成功(类型断言仍然是必要的,因为如果Foo具有其他属性,则{name: 'Foo'}类型的值可能不是Foo )。
https://stackoverflow.com/questions/73280790
复制相似问题