[英]Typescript: interface that extends a JSON type
我在 typescript 中使用通用 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;
}
這目前失敗了,因為 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'.
但是從分析上我們當然知道這是可以的,因為我們認識到在運行時類型 Foo 和 Bar是JSON 兼容的。 我如何告訴 typescript 這是一個好的演員陣容?
ETA:我可以按照錯誤消息先轉換為未知,但我寧願不這樣做——如果 TS 真正理解差異會更好,我想知道這是否可能。
這里的問題是編譯器不使用檢查if ('name' in data && data['name'] === 'FOO')
來 縮小data
類型從其原始類型{[key: string]: JSONValue}
。 {[key: string]: JSONValue}
類型不是union ,當前in
運算符僅檢查union 類型的窄值。 在microsoft/TypeScript#21732有一個開放的功能請求來做這樣的縮小,但現在它不是語言的一部分。
這意味着data
在檢查后保持{[key: string]: JSONValue}
類型。 然后,當您嘗試通過data as Foo
斷言data
的類型為Foo
時,編譯器會警告您您可能犯了一個錯誤,因為它看不到Foo
和{[key: string]: JSONValue}
是足夠相關。
如果你確定你所做的是一個很好的檢查,你總是可以使用編譯器建議和類型斷言到與Foo
和{[key: string]: JSONValue}
相關的中間類型,例如unknown
:
return data as unknown as Foo; // okay
如果這與您有關,那么您可以編寫自己的用戶定義類型保護 function執行您期望的縮小范圍if ('name' in data && data['name'] === 'FOO')
。 本質上,如果該檢查通過,那么我們知道data
的類型是{name: 'FOO'}
,它與Foo
足夠相關以進行類型斷言。 這是一個可能的類型保護 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;
}
因此,而不是if ('name' in data && data['name'] === 'FOO')
,你寫if (hasKeyVal(data, 'name', 'FOO'))
。 返回類型obj is {[P in K]: V}
意味着如果 function 返回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
足夠相關,以便類型斷言成功(類型斷言仍然是必要的,因為類型{name: 'Foo'}
可能不是Foo
如果Foo
有其他屬性)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.