TypeScript省略许多嵌套属性

hk8txs48  于 2023-03-31  发布在  TypeScript
关注(0)|答案(1)|浏览(107)

我正在尝试寻找一种方法来省略接口中n数目的父属性和嵌套属性。在下面的例子中,我正在尝试省略一个父属性和两个嵌套属性。Omit类型只能达到一个级别的深度。我能够find something close,但它仍然不起作用:

interface IFirstObject {
  id: string;
  revisionId: string;
  someProperty: ISecondObject;
}

interface ISecondObject {
  somePropertyToKeep: string;
  firstPropertyToOmit: string;
  secondPropertyToOmit: string;
}

/// How can we create a type to behave like the below result I am looking for?
type SomeNestedOmitType<TEntity, TOmissions> = {...}

// Desired Result
const myObject: SomeNestedOmitType<IFirstObject, "id" | "someProperty.firstPropertyToOmit" | "someProperty.secondPropertyToOmit"> = {
  revisionId: "some revision id",
  someProperty: {
    somePropertyToKeep: "some property to keep"
  }
}

TypeScript中有没有办法做到这一点?
使用TypeScript 5

wwwo4jvm

wwwo4jvm1#

我下面给出的解决方案可以按照问题中的示例代码的预期工作。可能的未来读者应该注意:根据我的经验,这种深度嵌套的类型Map往往会有奇怪的边缘情况,如果不能接受,有时可能需要完全重构来解决。
下面是NestedOmit<T, K>的一个可能实现:

type NestedOmit<T, K extends PropertyKey> = {
    [P in keyof T as P extends K ? never : P]:
    NestedOmit<T[P], K extends `${Exclude<P, symbol>}.${infer R}` ? R : never>
}

这使用键重Map来抑制直接出现在K中的T的任何键;这部分只是“正常”Omit的替代品,如microsoft/TypeScript#41383中所述,如下所示:

type NormalOmit<T, K extends PropertyKey> = { 
  [P in keyof T as P extends K ? never : P]: T[P] 
}

Omit<T, K>NestedOmit<T, K>之间的差异发生在Map的属性值类型内部,而不是键。

type StripKey<K extends PropertyKey, P extends PropertyKey> =
    K extends `${Exclude<P, symbol>}.${infer R}` ? R : never

由于NestedOmit的属性是按照NestedOmit编写的,所以它是一个递归定义的类型。StripKey<T, P>的思想是将当前键PK的每个联合成员的开头的一个点进行跳转,或者如果成员不是以P和点开头,则完全抑制该成员。它使用一个分布式条件类型来独立地操作K的每个联合成员,并使用模板文字类型推断来解析字符串。让我们看看这部分的操作:

type Demo = StripKey<"a.b" | "a.c.d" | "e" | "e.f" | "e.g", "a">;
// type Demo = "b" | "c.d"

您可以看到,如果您将NestedOmit<{a: XYZ}, K>K作为"a.b" | "a.c.d" | "e" | "e.f" | "e.g", "a"调用,则a属性最终将成为NestedOmit<XYZ, "b" | "c.d">
这就是它的工作原理。让我们在你的例子中测试一下:

interface IFirstObject {
    id: string;
    revisionId: string;
    someProperty: ISecondObject;
}

interface ISecondObject {
    somePropertyToKeep: string;
    firstPropertyToOmit: string;
    secondPropertyToOmit: string;
}

type N = NestedOmit<IFirstObject,
    "id" | "someProperty.firstPropertyToOmit" | "someProperty.secondPropertyToOmit"
>;
/* type N = {
    revisionId: string;
    someProperty: NestedOmit<ISecondObject, 
      "firstPropertyToOmit" | "secondPropertyToOmit"
    >;
} */

该类型是正确的,但显示可能不是您想要看到的,因为它是显式地以NestedOmit的形式编写的。如果重要的话,我们可以使用How can I see the full expanded contract of a Typescript type?中描述的技术来使NestedOmit完全扩展其类型:

type NestedOmit<T, K extends PropertyKey> = {
    [P in keyof T as P extends K ? never : P]:
    NestedOmit<T[P], K extends `${Exclude<P, symbol>}.${infer R}` ? R : never>
} extends infer O ? { [P in keyof O]: O[P] } : never;

⋯ extends infer O ? { [P in keyof O]: O[P] } : never就是实现这一点的部件。现在我们看到:

type N = NestedOmit<IFirstObject,
    "id" | "someProperty.firstPropertyToOmit" | "someProperty.secondPropertyToOmit"
>;
/* type N = {
    revisionId: string;
    someProperty: {
        somePropertyToKeep: string;
    };
} */

这正是你想要的
Playground代码链接

相关问题