简体   繁体   English

Typescript:断言未知输入的类型为 Pick<concretetype, subset of keys concretetype> 对于指定的键</concretetype,>

[英]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:在那种情况下,我会这样处理:

  1. Loop through keys checking that they exist遍历keys检查它们是否存在

  2. 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 −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^

Playground link 游乐场链接

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

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