我有一个函数可以验证 JSON 响应以确保它对应于给定的形状。
这是我定义所有可能的 JSON 值的类型——取自https://github.com/microsoft/TypeScript/issues/1897#issuecomment-338650717
type AnyJson = boolean | number | string | null | JsonArray | JsonMap;
type JsonMap = { [key: string]: AnyJson };
type JsonArray = AnyJson[];
Run Code Online (Sandbox Code Playgroud)
现在我有一个函数来进行验证,给定要验证的对象和一个具有 shape 的模拟对象T。
function isValid<T extends AnyJson>(obj: AnyJson, shape: T): obj is T {
// ... implementation
}
Run Code Online (Sandbox Code Playgroud)
但是,当我尝试使用接口和真实对象调用该函数时,我Thing在类型参数下收到类型错误
interface Response {
Data: Thing[]; // Thing is an interface defined elsewhere
};
isValid<Response>(data, { Data: [] })
// ^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
Type 'Response' does not satisfy the constraint 'AnyJson'.
Type 'Response' is not assignable to type 'JsonMap'.
Index signature is missing in type 'Response'.
Run Code Online (Sandbox Code Playgroud)
奇怪的是,当Response是类型而不是接口时不会发生这种情况,例如
type Response = {
Data: Thing[];
};
Run Code Online (Sandbox Code Playgroud)
但是后来我确实遇到了相同的错误,但在Thing其本身上又进一步降低了一个级别,这仍然是一个接口:
Type 'Response' does not satisfy the constraint 'AnyJson'.
Type 'Response' is not assignable to type 'JsonMap'.
Property 'Data' is incompatible with index signature.
Type 'Thing[]' is not assignable to type 'AnyJson'.
Type 'Thing[]' is not assignable to type 'JsonArray'.
Type 'Thing' is not assignable to type 'AnyJson'.
Type 'Thing' is not assignable to type 'JsonMap'.
Index signature is missing in type 'Thing'.
Run Code Online (Sandbox Code Playgroud)
所以我的问题是为什么这种预期的缩小不会发生在接口上,而只会发生在类型上?
It's a known issue (see microsoft/TypeScript#15300) that implicit index signatures are only inferred for object literals and for type aliases, and not for interface or class types. It's currently by design; inferring implicit index signatures in the absence of exact types is not type safe. For example, a value of type Response is not known to only have a Data property. It may have a property incompatible with AnyJson (e.g., interface FunkyResponse extends Response { otherProp: ()=>void }) So the compiler refuses to infer an implicit index signature there. It's technically unsafe to do this for type aliases, too, but for whatever reason one is allowed and the other is not. If you want to see this changed you might want to go to that issue and give it a and/or describe your use case if you think it's compelling. Actually it looks like someone has mentioned this use case already.
那么,除非这个问题得到解决,否则我们能做什么?一般来说,在这些情况下,我发现将我想要的类型表示为通用约束而不是具体类型更容易。索引签名被替换为映射类型。目标是提出一个通用类型别名,JsonConstraint<T>这样有效的 JSON 类型(如)Response将可分配给JsonConstraint<Response>,但无效的 JSON 类型(如)Date将不可分配给JsonConstraint<Date>。这是我可能会写的一种方式:
type JsonConstraint<T> = boolean | number | string | null | (
T extends Function ? never :
T extends object ? { [K in keyof T]: JsonConstraint<T[K]> }
: never
)
Run Code Online (Sandbox Code Playgroud)
如果是可接受的基本类型之一,则为T extends JsonConstraint<T>true ,如果是函数,则为 false,否则它会递归到 的属性并检查每个类型。此递归应该适用于对象和数组,因为TypeScript 3.1 引入了映射元组/数组类型。TTT
现在我想编写函数签名isValid<T extends JsonConstraint<T>>(obj: AnyJson, shape: T): obj is AnyJson & T,但这是一个不可接受的循环约束。有时会发生这种情况。解决此问题的一种方法是将签名更改为isValid<T>(obj: AnyJson, shape: T & JsonConstraint<T>): obj is AnyJson & T. 这将从 推断T,shape然后检查是否JsonConstraint<T>仍可分配给shape。如果是这样,那就太好了。如果不是,该错误应该提供信息。
所以这里是isValid():
function isValid<T>(obj: AnyJson, shape: T & JsonConstraint<T>): obj is typeof obj & T {
return null!; // impl here
}
Run Code Online (Sandbox Code Playgroud)
现在让我们测试一下:
declare const data: AnyJson
declare const response: Response;
if (isValid(data, response)) {
data.Data.length; // okay
};
Run Code Online (Sandbox Code Playgroud)
这样就可以正常工作,正如您所希望的那样。让我们看看它的行为是否符合其他类型的预期。我们不应该将其用作undefined属性类型:
isValid(data, { undefinedProp: undefined }); // error!
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Types of property 'undefinedProp' are incompatible
Run Code Online (Sandbox Code Playgroud)
或者函数值属性:
isValid(data, { deeply: { nested: { property: { func: () => 1 } } } }); // error!
// Types of property 'func' are incompatible.
Run Code Online (Sandbox Code Playgroud)
或者 a Date(失败是因为它有各种不可序列化的方法):
isValid(data, new Date()); // error!
// Types of property 'toString' are incompatible.
Run Code Online (Sandbox Code Playgroud)
最后,我们应该能够毫无错误地使用string、number、boolean、null和 数组/对象:
isValid(data, {
str: "",
num: 1,
boo: Math.random() < 0.5,
nul: null,
arr: [1, 2, 3],
obj: { a: { b: ["a", true, null] } }
}); // no error
Run Code Online (Sandbox Code Playgroud)
看起来不错。好的,希望有帮助;祝你好运!
| 归档时间: |
|
| 查看次数: |
240 次 |
| 最近记录: |