属于特定值属性的嵌套泛型类型的打字稿字符串点表示法

Ali*_*eir 4 typescript

这个问题是这里问题的延伸。

我有一个对象:

type exampleType = {
    propertyOne: string
    propertyTwo: number,
    propertyThree: {
        propertyFour: string,
        propertyFive: Date,
        propertySix: boolean,
    }
}
Run Code Online (Sandbox Code Playgroud)

string我正在寻找一种类型,可以验证点符号(如字符串)或的路径Date。在上面的示例中,这意味着该类型编译为:

propertyOne | propertyThree.propertyFour | propertyThree.PropertyFive
Run Code Online (Sandbox Code Playgroud)

使用上面先前提出的问题,可以进行以下操作:

type PathsToStringProps<T> = T extends string ? [] : {
    [K in Extract<keyof T, string>]: [K, ...PathsToStringProps<T[K]>]
}[Extract<keyof T, string>];

type Join<T extends string[], D extends string> =
    T extends [] ? never :
    T extends [infer F] ? F :
    T extends [infer F, ...infer R] ?
    F extends string ? 
    `${F}${D}${Join<Extract<R, string[]>, D>}` : never : string;  

type Path = Join<PathsToStringProps<exampleType>, ".">
Run Code Online (Sandbox Code Playgroud)

我试图使上述解决方案变得通用,以便我可以给出Path两个通用参数:T,它代表exampleType此处, 和V,这将string|Date在我上面的示例中。

当我尝试exampleType通用时:

type Path<T> = Join<PathsToStringProps<T>, ".">
Run Code Online (Sandbox Code Playgroud)

我收到这个错误:Excessive stack depth comparing types 'PathsToStringProps<T>' and 'string[]'.ts(2321)

我可以通过指定 T 必须表示键值对象来解决这个问题:

type Path<T extends {[key: string]: any}> = Join<PathsToStringProps<T>, ".">
Run Code Online (Sandbox Code Playgroud)

继续将值的类型限制为路径指向:

type PathsToStringProps<T, V> = T extends (V) ? [] : {
    [K in Extract<keyof T, string>]: [K, ...PathsToStringProps<T[K], V>]
}[Extract<keyof T, string>];

type Join<T extends string[], D extends string> =
    T extends [] ? never :
    T extends [infer F] ? F :
    T extends [infer F, ...infer R] ?
    F extends string ? 
    `${F}${D}${Join<Extract<R, string[]>, D>}` : never : string;  

type Path<T extends {[key: string]: any}, V> = Join<PathsToStringProps<T, V>, ".">
Run Code Online (Sandbox Code Playgroud)

但我收到一个错误:

错误

如果我从 中删除通用参数 V Path,但将其保留在 中,该参数就会消失PathsToStringProps

type Path<T extends {[key: string]: any}> = Join<PathsToStringProps<T, string|Date>, ".">
Run Code Online (Sandbox Code Playgroud)

这是我最后一次尝试让它发挥作用的 TypeScript Playground。

jca*_*alz 5

您的方法,其中PathsToProps<T, V>生成路径为元组,然后Join<T, D>连接元组元素以形成点路径,这对编译器来说是有问题的,因为 和PathToProps<T, V>都是Join<T, D>递归条件类型,它们并不总是很好地组合,并且经常与循环发生冲突守卫或性能问题。也许你可以调整一些东西以使它正常工作,但这不是我的第一选择。

相反,由于您似乎根本不关心元组,因此您可以直接在 内部连接字符串PathsToProps<T, V>。换句话说,您不是先构建路径然后再连接它们,而是在整个过程中进行连接。

它可能看起来像这样:

type PathsToProps<T, V> = T extends V ? "" : {
    [K in Extract<keyof T, string>]: Dot<K, PathsToProps<T[K], V>>
}[Extract<keyof T, string>];

type Dot<T extends string, U extends string> = 
  "" extends U ? T : `${T}.${U}`
Run Code Online (Sandbox Code Playgroud)

的实现PathsToProps与您的非常相似,除了我们不是显式处理空元组[]并通过 前置到元组中[K, ...PathsToProps<T[K], V>>],而是显式使用空字符串""并通过 串联它们Dot<K, PathsToProps<T[K], V>

Dot<T, U>类型只是连接字符串T与已经点分字符串的简写U。你在它们之间放一个点,除非U是空的(如果你有任何对象以空字符串作为键,这在技术上是错误的。你不会,是吗?我希望不会)。关键是要确保您不会以需要删除的尾随点结束(如果您总是用点连接,那么您会得到类似的路径"foo.bar.baz.")。

让我们测试一下:

type ExampleType = {
    propertyOne: string
    propertyTwo: number,
    propertyThree: {
        propertyFour: string,
        propertyFive: Date,
        propertySix: boolean,
    }
}

type StrOrDateEx = PathsToProps<ExampleType, string | Date>
// type StrOrDateEx = "propertyOne" | "propertyThree.propertyFour" | 
//  "propertyThree.propertyFive"
Run Code Online (Sandbox Code Playgroud)

看起来不错!并且没有递归警告。

Playground 代码链接