[英]Typescript: Assert unknown input has type Pick<ConcreteType, subset of keys of ConcreteType> for specified keys
When trying to create a generic function to test if an unknown input is a subset of a known object type I run into trouble with Typescript. I want to specify which keys should be present and assert that the input is of type Pick<ConcreteType, subset of keys of ConcreteType>.当尝试创建一个通用的 function 来测试未知输入是否是已知 object 类型的子集时,我遇到了 Typescript 的问题。我想指定应该存在哪些键并断言输入的类型为 Pick<ConcreteType, subset ConcreteType> 的键数。 My assertion
我的主张
Simplified code:简化代码:
type Rectangle = {
width: number,
height: number
}
const assertObject: (o: unknown) => asserts o is Record<PropertyKey, unknown> = (
o,
) => {
if (typeof o !== `object`) {
throw new Error();
}
};
function assertRectangle <K extends keyof Rectangle>(o: unknown, ...keys: K[]): asserts o is Pick<Rectangle, K> {
assertObject(o);
// >>>>>>>>>>>>>>>>>>>>>> HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
if (keys.includes(`width` as K) && !o.hasOwnProperty('width')) {
throw new Error('');
}
if (keys.includes(`height` as K) && !o.hasOwnProperty('height')) {
throw new Error('');
}
}
const rect = {width: 1, height: 1};
assertRectangle(rect, 'width'); // Pick<Rectangle, "width">
assertRectangle(rect, 'height', 'width'); // Pick<Rectangle, "height" | "width">
This code works but not if we remove the as K
inside keys.includes
.这段代码有效,但如果我们删除
keys.includes
中的as K
则无效。
if (keys.includes(`width`) && !o.hasOwnProperty('width')) {
throw new Error('');
}
// OR:
if (keys.includes(`width` as const) && !o.hasOwnProperty('width')) {
throw new Error('');
}
Argument of type '"width"' is not assignable to parameter of type 'K'.
“宽度”类型的参数不可分配给“K”类型的参数。 '"width"' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'keyof Rectangle'.ts(2345)
“宽度”可分配给类型“K”的约束,但“K”可以用约束“keyof Rectangle”的不同子类型实例化。ts(2345)
I was wondering why as const
doesn't work here, the problem I am wondering about is that when I or a colleague decide to change or rename properties on the type Rectangle, I would like typescript to warn me when this assert does not longer cover the type.我想知道为什么
as const
在这里不起作用,我想知道的问题是当我或同事决定更改或重命名 Rectangle 类型的属性时,我希望 typescript 在这个断言不再涵盖时警告我方式。 Adding, renaming or subtracting a property on the type will not get catched by this assert.添加、重命名或减去类型的属性不会被此断言捕获。
In a comment you've said:在评论中你说:
I am doing it in this way because each property/key could have seperate checks.
我这样做是因为每个属性/键都可以有单独的检查。 For example the Rectangle can have an id field and I would also want to assert if that id is of type string and matches a uuid regex
例如 Rectangle 可以有一个 id 字段,我还想断言该 id 是否属于字符串类型并匹配 uuid 正则表达式
In that case, I'd approach it like this:在那种情况下,我会这样处理:
Loop through keys
checking that they exist遍历
keys
检查它们是否存在
For properties that need additional checks, use if
/ else if
to identify them and apply the extra checks.对于需要额外检查的属性,使用
if
/ else if
来识别它们并应用额外检查。 (I was surprised to find that switch
doesn't complain about cases that can never be reached, but if
does.) (我很惊讶地发现
switch
不会抱怨永远无法到达的情况,但if
会的话。)
Something like the following — note that in this example I have a check for a property that doesn't exist on Rectangle
, and TypeScript warns me about that.类似于以下内容 - 请注意,在此示例中,我检查了
Rectangle
上不存在的属性,TypeScript 就此警告我。 This is to demonstrate your "I am wondering about is that when I or a colleague decide to change or rename properties on the type Rectangle" situation (in this case, let's say Rectangle
used to have id
but doesn't anymore).这是为了证明您的“我想知道的是,当我或同事决定更改或重命名 Rectangle 类型的属性时”的情况(在这种情况下,假设
Rectangle
曾经有id
但现在没有了)。
type Rectangle = {
width: number,
height: number;
};
const assertObject: (o: unknown) => asserts o is Record<PropertyKey, unknown> = (
o,
) => {
if (o === null || typeof o !== `object`) {
// ^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− added
throw new Error();
}
};
function assertRectangle<K extends keyof Rectangle>(
o: unknown,
...keys: K[]
): asserts o is Pick<Rectangle, K> {
assertObject(o);
for (const key of keys) {
// Basic check
if (!o.hasOwnProperty(key)) {
throw new Error("");
}
// Additional per-property checks
// (I was surprised that `switch` didn't work here to call out property
// names that aren't on Rectangle like "id" below.)
if (key === "width" || key === "height") {
if (typeof o[key] !== "number") {
throw new Error(`typeof of '${key}' expected to be 'number'`);
}
} else if (key === "id") {
// ^^^^^^^^^^^^−−−−−−−− causes error because `id` isn't a valid
// Rectangle property (e.g., if you remove
// a property from `Rectangle`, TypeScript
// warns you)
const id = o[key];
if (typeof id !== "string" || !id) {
throw new Error(`'${key}' expected to be non-empty string`);
}
}
}
}
declare let rect1: unknown;
declare let rect2: unknown;
declare let rect3: unknown;
assertRectangle(rect1, "width");
rect1; // <== type is Pick<Rectangle, "width">
assertRectangle(rect2, "height", "width");
rect2; // <== type is Pick<Rectangle, "height" | "width">
assertRectangle(rect3, "height", "width", "not-rectangle-property");
// Error −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.