如何递归地从类型中省略键 [英] How to recursively Omit key from type

查看:51
本文介绍了如何递归地从类型中省略键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写一个递归省略字段的类型实用程序.你会像这样命名和使用的东西 OmitRecursively

我尝试使用映射类型 + 条件类型来实现,但是当所有必填字段都被正确输入时我仍然坚持这种情况(因此字段从嵌套类型中消失),但该方法忽略了可选字段.

//这是一个递归删除 __typename 字段的函数//Appolo 客户端添加的type Deapolify=省略<{ [P in keyof T]: T[P] extends { __typename: string } ?去极化 T[P]: T[P] },'__typename'>//或更通用的尝试type OmitRecursively=省略<{ [P in keyof T]: T[P] extends any ?省略 T[P],K: 绝不 },钾>

预期行为将是 root,并且所有嵌套键的类型都应该递归省略.例如

type A = {keyToKeep: 字符串keyToOmit:字符串嵌套:{keyToKeep: 字符串keyToOmit:字符串}嵌套可选?:{keyToKeep: 字符串keyToOmit:字符串}}type Result = OmitRecursively类型预期 = {keyToKeep: 字符串嵌套:{keyToKeep: 字符串}嵌套可选?:{keyToKeep: 字符串}}预期 === 结果

解决方案

你不会递归调用OmitRecursevly,如果属性类型是对象,我也只会递归应用省略,否则它应该主要工作:

<预><代码>type OmitDistributive= T 扩展任何 ?(T扩展对象?Id:T):从不;类型Id T= {} &{ [P in keyof T] : T[P]}//装饰使用只使tooltips expad 类型可以去掉type OmitRecursively=省略<{ [P in keyof T]: OmitDistributive<T[P], K>},钾>类型 A = {keyToKeep: 字符串keyToOmit:字符串嵌套:{keyToKeep: 字符串keyToOmit:字符串}嵌套可选?:{keyToKeep: 字符串keyToOmit:字符串}}type Result = OmitRecursively

游乐场链接

编辑:更新以反映内置Omit 辅助类型的添加.对于旧版本,只需定义 Omit.

注意 Id 主要用于美观的原因(它强制编译器在工具提示中扩展 Pick)并且可以删除,有时在某些核心情况下可能会导致问题.

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

说明

默认情况下,Omit 类型不适用于联合.Omit 将联合视为一个整体,不会从联合的每个成员中提取属性.这主要是因为 keyof 只会返回联合的公共属性(所以 keyof undefined | { a: number } 实际上是 nevercode>,因为没有共同的属性).

幸运的是,有一种方法可以使用条件类型来钻取联合.条件类型将分布在裸类型参数上(参见 此处 用于我的解释或 docs).对于 OmitDistributive,我们并不真正关心条件(这就是我们使用 T extends any 的原因),我们只关心是否使用条件类型 T 将依次成为工会中的每个成员.

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

OmitDistributive<{ a: number, b: number} |未定义}, 'a'>=OmitRecursively<{a: number, b: number}, 'a'>|不明确的

I want to write a type utility that omits fields recursively. Something that you would name and use like that OmitRecursively<SomeType, 'keyToOmit'>

I've tried to do it using mapped types + conditional typing but I stuck on the case when all required fields got typed correctly (hence field disappeared from nested type), but optional fields are ignored with that approach.

// 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
>

Expected behavior would be root and all nested keys that have a type with a key that should be recursively omitted is omitted. E.g

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

解决方案

You don't call OmitRecursevly recursively, and I also would only apply the omit recursively if the property type is an object, otherwise it should mostly work:


type OmitDistributive<T, K extends PropertyKey> = 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 extends PropertyKey> = 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'>

Playground link

Edit: Updated to reflect addition of built-in Omit helper type. For older versions just define Omit.

Note Id is used mostly for cosmetic reasons (it forces the compiler to expand Pick in tooltips) and can be removed, it sometimes can cause problems in some corener cases.

Edit The original code did not work with strictNullChecks because the type of the property was type | undefined. I edited the code to distribute over unions. The conditional type OmitDistributive is used for its distributive behavior (we use it for this reason not the condition). This means that OmitRecursively will be applied to each member of the union.

Explanations

By default the Omit type does not work well on unions. Omit looks at a union as a whole and will not extract properties from each member of the union. This is mostly due to the fact that keyof will only return common properties of a union (so keyof undefined | { a: number } will actually be never, since there are no common properties).

Fortunately there is a way to drill into a union using conditional type. Conditional types will distribute over naked type parameters (see here for my explanation or the docs). In the case of OmitDistributive we don't really care about the condition (that is why we use T extends any) we just care that if we use conditional types T will in turn be each member in the union.

This means that these types are equivalent:

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

这篇关于如何递归地从类型中省略键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆