好的,这是一个很长很具体的内容。非常感谢您的投入。
我写了一个递归Omit
类型,它使用一个T
类型和一个字符串元组(“路径”),并索引到T
中,删除了最后一个项目在路径上并返回该类型。
我已经看过Deep Omit With Typescript了-但这涉及递归地省略相同的键,而不是沿路径遍历。
我也看过Type Safe Omit Function,但这涉及到运行时,大约是一个一级的省略。
我的实现:
// for going along the tuple that is the path
type Tail<T extends any[]> = ((...args: T) => void) extends ((head: unknown, ...rest: infer U) => void) ? U : never;
type DeepOmit<T, Path extends string[]> = T extends object ? {
0: Omit<T, Path[0]>;
1: { [K in keyof T]: K extends Path[0] ? DeepOmit<T[K], Tail<Path>> : T[K] }
}[Path['length'] extends 1 ? 0 : 1] : T;
我想这样做,因此Path
被约束为对T
的有效遍历,例如:
Path = [K1, K2, K3, ..., K_n] such that:
K1 extends keyof T, K2 extends keyof T[K1], ... Kn extends keyof[T][K1]...[K_(n-1)]
首次尝试
我编写了一个类型,该类型采用类型T
和路径P
,如果有效,则返回P
,否则返回never
type ExistingPathInObject<T, P extends any[]> = P extends [] ? P : {
0: ExistingPathInObject<T[P[0]], Tail<P>> extends never ? never : P;
1: never;
}[P[0] extends keyof T ? 0 : 1]
但是,在DeepOmit
的签名中,我无法强制执行Path extends ExistingPathInObject<T,Path>
,因为这是循环依赖项。我之所以提及这种尝试,是因为可能有一种方法可以规避圆度并使用此类型来验证Path
是对T
的有效遍历。
第二次尝试
因为我似乎无法使用Path
来约束自己,所以我尝试生成类型中所有现有路径的并集,然后要求Path
进行扩展。我能想到的最好的方法是:
type Paths<T> = {
0: { [K in keyof T]: T[K] extends object ? [K, Paths<T[K]>[keyof Paths<T[K]>]] : [K] }
1: []
}[T extends object ? 0 : 1];
// an example type to test on
type HasNested = { a: string; b: { c: number; d: string } };
type pathTest = Paths<HasNested>;
//{
// a: ["a"];
// b: ["b", ["d"] | ["c"]];
//}
type pathTestUnion = Paths<HasNested>[keyof Paths<HasNested>]
// ["a"] | ["b", ["d"] | ["c"]]
这使我可以匹配写为树的路径:['a'] extends pathTestUnion
和['b', ['d']] extends pathTestUnion
都是正确的。我必须添加一个[keyof T]
才能获得并集,但我不能将其放在类型Paths
中,因为它未被识别为有效。
完成所有这些操作后,我现在很难重写DeepOmit
以使用此约束。这是我尝试过的:
type Types<T> = T[keyof T];
type TypeSafeDeepOmit<T, Path extends Types<Paths<T>>, K extends keyof T> =
Path extends any[] ?
T extends object ?
{ // T is object and Path is any[]
0: Omit<T, K>;
1: { [P in keyof T]: P extends Path[0] ? TypeSafeDeepOmit<T[P], Path[1], Path[1][0]> : T[P] }
}[Path['length'] extends 1 ? 0 : 1] :
T : // if T is not object
never; // if Path is not any[]
type TSDO_Helper<T, P extends Types<Paths<T>>> = P extends any[] ? P[0] extends keyof T ? TypeSafeDeepOmit<T, P, P[0]> : never : never;
这很丑陋,并且使用助手类型来实际工作。我还必须告诉编译器P extends any[]
和P[0] extends keyof T
,尽管Paths
正是要确保这一点。使用TypeSafeDeepOmit
-
Path[1]
递归调用中也出现了错误
Type 'any[] & Path' is not assignable to type '[]'.
Types of property 'length' are incompatible.
Type 'number' is not assignable to type '0'
我已通过设置Path extends Types<Paths<T>> | []
来解决此问题,但是我不确定这是正确的方法。
总结
那么,有没有更好的方法来强制执行有效路径?是否还可以支持路径的合并,从而将所有路径都忽略掉?现在,我得到的结果是各种省略结果的结合。
答案 0 :(得分:2)
这个评论太长了,但是我不知道它是否可以作为答案。
这是一个真正的令人讨厌的转换(有许多可能的极端情况),从省略组合到多省略:
type NestedId<T, Z extends keyof any = keyof T> = (
[T] extends [object] ? { [K in Z]: K extends keyof T ? NestedId<T[K]> : never } : T
) extends infer P ? { [K in keyof P]: P[K] } : never;
type MultiDeepOmit<T, P extends string[]> = NestedId<P extends any ? DeepOmit<T, P> : never>
它的工作方式(如果可以工作)是采用{a: string, b: number} | {b: number, c: boolean}
之类的联合,并且仅使用存在于联合的所有组成部分中的键:{b: number}
。很难做到这一点,并且破坏了可选属性以及谁知道其他什么。
祝你好运,对不起,没有好的答复。