[英]Typescript: interface that extends a JSON type
I am using a generic JSON type in typescript, suggested from here我在 typescript 中使用通用 JSON 类型,建议从这里
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| {[key: string]: JSONValue}
I want to be able to cast from interface types that match JSON to and from the JSON type.我希望能够从与 JSON 匹配的接口类型转换为 JSON 类型。 For example:例如:
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;
}
This currently fails because Typescript does not see how the interface could be of the same type as JSONValue:这目前失败了,因为 Typescript 看不到接口如何与 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'.
but analytically we of course know this is ok, because we recognize that at runtime types Foo and Bar are JSON compatible.但是从分析上我们当然知道这是可以的,因为我们认识到在运行时类型 Foo 和 Bar是JSON 兼容的。 How do I tell typescript that this is an ok cast?我如何告诉 typescript 这是一个好的演员阵容?
ETA: I can follow the error message and cast to unknown first, but I'd rather not do that -- it would be better if TS actually understood the difference, and I'm wondering if it's possible at all. ETA:我可以按照错误消息先转换为未知,但我宁愿不这样做——如果 TS 真正理解差异会更好,我想知道这是否可能。
The issue here is that the compiler does not use the check if ('name' in data && data['name'] === 'FOO')
to narrow the type of data
from its original type of {[key: string]: JSONValue}
.这里的问题是编译器不使用检查if ('name' in data && data['name'] === 'FOO')
来 缩小data
类型从其原始类型{[key: string]: JSONValue}
。 The type {[key: string]: JSONValue}
is not a union , and currently in
operator checks only narrow values of union types. {[key: string]: JSONValue}
类型不是union ,当前in
运算符仅检查union 类型的窄值。 There is an open feature request at microsoft/TypeScript#21732 to do such narrowing, but for now it's not part of the language.在microsoft/TypeScript#21732有一个开放的功能请求来做这样的缩小,但现在它不是语言的一部分。
That means data
stays of type {[key: string]: JSONValue}
after the check.这意味着data
在检查后保持{[key: string]: JSONValue}
类型。 When you then try to assert that data
is of type Foo
via data as Foo
, the compiler warns you that you might be making a mistake, because it doesn't see Foo
and {[key: string]: JSONValue}
are types that are related enough.然后,当您尝试通过data as Foo
断言data
的类型为Foo
时,编译器会警告您您可能犯了一个错误,因为它看不到Foo
和{[key: string]: JSONValue}
是足够相关。
If you are sure that what you're doing is a good check, you could always do with the compiler suggests and type-assert to an intermediate type which is related to both Foo
and {[key: string]: JSONValue}
, such as unknown
:如果你确定你所做的是一个很好的检查,你总是可以使用编译器建议和类型断言到与Foo
和{[key: string]: JSONValue}
相关的中间类型,例如unknown
:
return data as unknown as Foo; // okay
If that concerns you then you can write your own user defined type guard function which performs the sort of narrowing you expect from if ('name' in data && data['name'] === 'FOO')
.如果这与您有关,那么您可以编写自己的用户定义类型保护 function执行您期望的缩小范围if ('name' in data && data['name'] === 'FOO')
。 Essentially if that check passes, then we know that data
is of type {name: 'FOO'}
, which is related enough to Foo
for a type assertion.本质上,如果该检查通过,那么我们知道data
的类型是{name: 'FOO'}
,它与Foo
足够相关以进行类型断言。 Here's a possible type guard function:这是一个可能的类型保护 function:
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;
}
So instead of if ('name' in data && data['name'] === 'FOO')
, you write if (hasKeyVal(data, 'name', 'FOO'))
.因此,而不是if ('name' in data && data['name'] === 'FOO')
,你写if (hasKeyVal(data, 'name', 'FOO'))
。 The return type obj is {[P in K]: V}
means that if the function returns true
, the compiler should narrow the type of obj
to something with a property whose key is of type K
and whose value is of type V
.返回类型obj is {[P in K]: V}
意味着如果 function 返回true
,编译器应将obj
的类型缩小为具有键为K
类型且值为V
类型的属性的类型。 Let's test it:让我们测试一下:
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;
}
Now it works.现在它起作用了。 The hasKeyVal()
check narrows data
to something with a name
property of the right type, and this is related enough to Foo
or Bar
for the type assertion to succeed (the type assertion is still necessary because a value of type {name: 'Foo'}
might not be a Foo
if Foo
has other properties). hasKeyVal()
检查将data
缩小到具有正确类型的name
属性的内容,这与Foo
或Bar
足够相关,以便类型断言成功(类型断言仍然是必要的,因为类型{name: 'Foo'}
可能不是Foo
如果Foo
有其他属性)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.