Typescript-类型安全的深度省略,或:如何列出合法对象路径

Typescript-类型安全的深度省略,或:如何列出合法对象路径,typescript,recursion,recursive-datastructures,typescript-generics,Typescript,Recursion,Recursive Datastructures,Typescript Generics,好的,这是一个很长很具体的问题。如有任何意见,将不胜感激 我编写了一个递归的ommit类型,它接受一个类型T和一个字符串元组(一个“路径”),并索引到T,删除路径上的最后一项并返回该类型 我已经看过了,但这涉及递归地省略同一个键,而不是沿着路径遍历 我也看过,但它涉及运行时,大约是一级省略 我的实施: //用于沿着作为路径的元组前进 类型Tail=(…args:T)=>void)扩展((head:unknown,…rest:inferu)=>void)?U:从来没有; 类型DeepOmit=T扩

好的,这是一个很长很具体的问题。如有任何意见,将不胜感激

我编写了一个递归的
ommit
类型,它接受一个类型
T
和一个字符串元组(一个“路径”),并索引到
T
,删除路径上的最后一项并返回该类型

我已经看过了,但这涉及递归地省略同一个键,而不是沿着路径遍历

我也看过,但它涉及运行时,大约是一级省略

我的实施:

//用于沿着作为路径的元组前进
类型Tail=(…args:T)=>void)扩展((head:unknown,…rest:inferu)=>void)?U:从来没有;
类型DeepOmit=T扩展对象?{
0:省略;
1:{[K in keyof T]:K扩展路径[0]?DeepOmit:T[K]}
}[Path['length']扩展1?0:1]:T;
我想使
路径
被约束为
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

类型ExistingPathInObject=P扩展[]?P:{
0:ExistingPathInObject从不扩展?从不:P;
1:从不;
}[P[0]扩展了T的键?0:1]
但是,在
DeepOmit
的签名中,我无法强制执行
路径扩展现有PathInObject
,因为这是一个循环依赖项。我之所以提到这种尝试,是因为可能有一种方法可以绕过循环,并使用这种类型来验证
Path
是否是
T
的有效遍历

第二次尝试

因为我似乎无法使用
Path
来约束自身,所以我尝试生成一个类型中所有现有路径的并集,然后要求
Path
来扩展它。我能想到的最好的办法是:

类型路径={
0:{[K in keyof T]:T[K]扩展对象?[K,路径[keyof path]]:[K]}
1: []
}[T扩展对象?0:1];
//要测试的示例类型
类型HasNested={a:string;b:{c:number;d:string};
类型pathTest=路径;
//{
//a:[“a”];
//b:[“b”、[“d”]|[“c”];
//}
类型pathTestUnion=Paths[keyof Paths]
//【a】【b】、【d】【c】】
这使我能够匹配一个以树形式编写的路径:
['a']扩展路径测试联盟
,而
['b',['d']]扩展路径测试联盟
都是真的。我必须添加一个
[keyof T]
来获得联合,并且我不能将它放入类型
路径
本身,因为它不能被识别为有效

完成所有这些之后,我现在很难重写
DeepOmit
来使用此约束。以下是我尝试过的:

类型类型=T[keyof T];
TypeSafeDeepOmit类型=
路径扩展任何[]?
T扩展对象?
{//T是对象,路径是任意[]
0:省略;
1:{[P in keyof T]:P扩展路径[0]?typesafedeepoimit:T[P]}
}[路径['length']扩展1?0:1]:
T://如果T不是对象
从不;//如果路径不是任何[]
类型TSDO_Helper=P扩展任何[]?P[0]扩展了T?类型:从不:从不;
这很难看,并且使用帮助器类型来实际工作。我还必须告诉编译器,
P扩展了any[]
P[0]扩展了keyof T
,即使这正是
路径
要确保的。使用
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 |[]
解决了这个问题,但我不确定这样做是否正确

总之


那么,有没有更好的方法来强制执行有效路径?是否也可以支持路径的并集,以便省略所有路径?现在我得到的结果是不同省略的结果的联合。

这对于评论来说太长了,但我不知道这是否算是一个答案

这里有一个从省略的并集到多重省略的非常令人讨厌的转换(有许多可能的边缘情况):

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>
类型NestedId=(
[T] 扩展[object]?{[K in Z]:K扩展T的键?NestedId:never}:T
)扩展推断P?{[K in keyof P]:P[K]}:永远不会;
类型MultiDeepOmit=NestedId
它的工作方式(如果它确实工作的话)是采用一个像
{a:string,b:number}{b:number,c:boolean}
这样的联合,并且只使用联合的所有组成部分中存在的键:
{b:number}
。很难做到这一点,而且会破坏可选属性,谁知道还有什么


祝你好运,很抱歉还没有很好的答案。

heh,从发现递归“hack”到发现它不被推荐,这24小时真是一团糟。哦,好吧,至少这是一个有趣的实验!谢谢你的链接和你的输入:)这很有趣,我需要浏览一下不同的部分,看看我是否理解它们。看起来你在最后推断
P
只是为了重建它,我想还有更多的东西。请注意,在此版本中,路径是一个字符串元组,而不是我能够生成的嵌套路径。您可以在
MultiDeepOmit
中更改
P
上的约束,而不影响其工作方式。在
NestedId
中,推断和重构卡车提示编译器在可能的情况下输出一个具体的对象类型,而不是一堆类型别名。我已将
NestedId
替换为
扩展推断P?P…
并且在我创建的几个基本类型上没有看到不同的结果。您能解释一下以其他方式生成的类型别名是什么样子的吗?不幸的是,我现在暂时不能测试这个。如果你要这样做,你最好替换
(XXX)extends inferp?P:永远不要使用
XX