简体   繁体   English

Typescript 属于某个值属性的嵌套泛型类型的字符串点表示法

[英]Typescript string dot notation of nested generic type that belongs to property of certain value

This question is an extension of the one found here .这个问题是此处找到的问题的扩展。

I have an object:我有一个 object:

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

I'm looking for a type that would validate a dot-notation like string to a path of either string or Date .我正在寻找一种可以将点符号(如 string)验证为stringDate的路径的类型。 In the example above, this would mean it the type compiles to:在上面的示例中,这意味着它的类型编译为:

propertyOne | propertyThree.propertyFour | propertyThree.PropertyFive

Using the question previously asked above, the following is possible:使用上面之前提出的问题,以下是可能的:

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>, ".">

I'm trying to make the above solution generic, so that I could give Path two generic arguments: T , which would represent exampleType here, and V , which would be string|Date in my example above.我正在尝试使上述解决方案通用,以便我可以给Path两个通用 arguments: T ,在这里代表exampleTypeV ,在我上面的示例中是string|Date

When I tried making exampleType generic:当我尝试使exampleType通用时:

type Path<T> = Join<PathsToStringProps<T>, ".">

I got this error: Excessive stack depth comparing types 'PathsToStringProps<T>' and 'string[]'.ts(2321)我得到了这个错误: Excessive stack depth comparing types 'PathsToStringProps<T>' and 'string[]'.ts(2321)

Which I was able to solve by specifying that T must represent a Key-Value object:我可以通过指定 T 必须代表键值 object 来解决这个问题:

type Path<T extends {[key: string]: any}> = Join<PathsToStringProps<T>, ".">

Moving on to restricting the type of value to path points to:继续将值的类型限制为路径指向:

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>, ".">

But I get an error:但我收到一个错误:

错误

Which disappears if I remove the generic argument V from Path , but keep it in PathsToStringProps :如果我从Path中删除通用参数 V ,它就会消失,但将其保留在PathsToStringProps中:

type Path<T extends {[key: string]: any}> = Join<PathsToStringProps<T, string|Date>, ".">

Here's a TypeScript Playground of my final attempt at getting this to work. 这是我最后一次尝试让它工作的 TypeScript Playground。

Your approach, where PathsToProps<T, V> generates paths as tuples , and then where Join<T, D> concatenates the tuple elements to form dotted paths, is problematic for the compiler, since both PathToProps<T, V> and Join<T, D> are recursive conditional types , which don't always compose very nicely, and often run afoul of circularity guards or performance problems.您的方法PathsToProps<T, V>将路径生成为tuples ,然后Join<T, D>将元组元素连接起来形成虚线路径,这对编译器来说是有问题的,因为PathToProps<T, V>Join<T, D>递归条件类型,它们的组合并不总是很好,并且经常与循环保护或性能问题发生冲突。 Maybe you could tweak things so as to get that working, but it wouldn't be my first choice.也许您可以调整一些东西以使其正常工作,但这不是我的第一选择。

Instead, since it doesn't seem like you actually care about the tuples at all, you could just concatenate the strings directly inside PathsToProps<T, V> .相反,由于您似乎根本不关心元组,您可以直接在PathsToProps<T, V>内连接字符串。 In other words, instead of building up the paths first and then concatenating them later, you concatenate throughout the process.换句话说,不是先建立路径,然后再将它们连接起来,而是在整个过程中连接起来。

It could look like this:它可能看起来像这样:

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}`

The implementation of PathsToProps is very similar to yours, except that instead of explicitly dealing with empty tuples [] and prepending to into tuples via [K, ...PathsToProps<T[K], V>>] , we explicitly use empty strings "" and concatenate them via Dot<K, PathsToProps<T[K], V> . PathsToProps的实现与您的非常相似,只是我们没有显式处理空元组[]并通过[K, ...PathsToProps<T[K], V>>]将其添加到元组中,而是显式使用空字符串""并通过Dot<K, PathsToProps<T[K], V>连接它们。

The Dot<T, U> type is just a shorthand for connecting a string T with an already-dotted-string U . Dot<T, U>类型只是将字符串T与已经加点的字符串U连接起来的简写。 You put a dot between them unless U is empty (this is technically wrong if you have any objects with an empty string as a key. You don't, do you? I hope not).你在它们之间放一个点,除非U是空的(如果你有任何带有空字符串作为键的对象,这在技术上是错误的。你没有,是吗?我希望不是)。 The point is to make sure that you don't end up with a trailing dot that you need to remove (if you always joined with a dot, then you would get paths like "foo.bar.baz." ).关键是要确保你最终不会得到一个需要删除的尾随点(如果你总是用一个点连接,那么你会得到像"foo.bar.baz."这样的路径)。

Let's test it out:让我们测试一下:

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"

Looks good.看起来不错。 And no recursion warnings.并且没有递归警告。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM