如何从类型中递归地省略键

时间:2019-02-01 21:05:15

标签: typescript recursion types

我想写一个类型实用程序,它递归地忽略字段。 您将使用OmitRecursively<SomeType, 'keyToOmit'>

这样的名称和名称

我尝试使用映射类型+条件类型来做到这一点,但是我坚持当所有必填字段都正确键入(因此字段从嵌套类型中消失了)的情况下,但是用这种方法会忽略可选字段。

// This is for one function that removes recursively __typename field 
// that Appolo client adds
type Deapolify<T extends { __typename: string }> = Omit<
  { [P in keyof T]: T[P] extends { __typename: string } ? Deapolify<T[P]> : T[P] },
  '__typename'
>

// Or more generic attempt

type OmitRecursively<T extends any, K extends keyof T> = Omit<
  { [P in keyof T]: T[P] extends any ? Omit<T[P], K> : never },
  K
>

预期的行为将是root,所有具有类型的应具有递归省略键的嵌套键都将被省略。 例如

type A = {
  keyToKeep: string
  keyToOmit: string
  nested: {
    keyToKeep: string
    keyToOmit: string
  }
  nestedOptional?: {
    keyToKeep: string
    keyToOmit: string
  }
}

type Result = OmitRecursively<A, 'keyToOmit'>

type Expected = {
  keyToKeep: string
  nested: {
    keyToKeep: string
  }
  nestedOptional?: {
    keyToKeep: string
  }
} 

Expected === Result

2 个答案:

答案 0 :(得分:3)

您不会递归调用OmitRecursevly,而且我也只会在属性类型是对象的情况下递归地应用省略,否则它应该可以正常工作:

// Or more generic attempt
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
type OmitDistributive<T, K> = T extends any ? (T extends object ? Id<OmitRecursively<T, K>> : T) : never;
type Id<T> = {} & { [P in keyof T] : T[P]} // Cosmetic use only makes the tooltips expad the type can be removed 
type OmitRecursively<T extends any, K> = Omit<
    { [P in keyof T]: OmitDistributive<T[P], K> },
    K
>

type A = {
    keyToKeep: string
    keyToOmit: string
    nested: {
        keyToKeep: string
        keyToOmit: string
    }
    nestedOptional?: {
        keyToKeep: string
        keyToOmit: string
    }
}

type Result = OmitRecursively<A, 'keyToOmit'>

注意 Id主要是出于装饰性原因(它会迫使编译器在工具提示中扩展Pick)并可以将其删除,有时可能会给某些核心人员带来问题案例。

修改 原始代码不适用于strictNullChecks,因为属性的类型为type | undefined。我编辑了代码以在工会上分发。条件类型OmitDistributive用于其分布行为(出于这个原因,我们使用它而不是条件)。这意味着,OmitRecursively将被应用到联合的每个成员。

Playground link

说明

默认情况下,Omit类型不适用于联合。 Omit长相在联合作为一个整体,也不会提取从联盟中的每个成员的属性。这主要是由于keyof仅返回联合的公共属性(因此,keyof undefined | { a: number }实际上将是never,因为没有公共属性)。

幸运的是,有一种方法可以使用条件类型深入到并集。有条件的类型将分布在裸类型参数(见here对我的解释或docs)。对于OmitDistributive,我们并不真正在意条件(这就是为什么使用T extends any的原因),我们只是关心如果我们使用条件类型T会成为每个成员在工会中。

这意味着这些类型是等效的:

OmitDistributive<{ a: number, b: number} | undefined}, 'a'> = 
     OmitRecursively<{ a: number, b: number}, 'a'> | undefined 

答案 1 :(得分:0)

当被Array包裹时,这不起作用。

type A = {
    keyToKeep: string
    keyToOmit: string
    nested: {
        keyToKeep: string
        keyToOmit: string
    }
    nestedOptional?: {
        keyToKeep: string
        keyToOmit: string
    }
    foo: Array<{
        keyToKeep: string
        keyToOmit: string
    }>
}