简体   繁体   English

Typescript:扩展 JSON 类型的接口

[英]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 和 BarJSON 兼容的。 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属性的内容,这与FooBar足够相关,以便类型断言成功(类型断言仍然是必要的,因为类型{name: 'Foo'}可能不是Foo如果Foo有其他属性)。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM