我要求一个对象类型不能在嵌套对象中重复键。例如,如果foo.bar
包含键hello
,那么foo.baz
就不能包含该键。有没有办法在类型级别上强制执行这一点?
一种简化的表述可能如下:
type NestedUniqueKeys<T extends Object> = any // <- what goes here?
interface Something {
one: string
two: string
three: string
four: string
}
const good: NestedUniqueKeys<Something> = {
foo: {
three: 'hi',
one: 'hi',
},
bar: {
two: 'hiya',
},
}
// @ts-expect-error
const bad: NestedUniqueKeys<Something> = {
foo: {
two: 'hi', // duplicated
one: 'hi',
},
bar: {
two: 'hiya', // duplicated
},
}
因此,一个更简单的步骤可能是,NestedUniqueKeys
如何被公式化为单层嵌套?
那么,如何将其推广到任意嵌套呢?
const good: NestedUniqueKeys<Something> = {
foo: {
three: 'hi',
baz: {
one: 'oh',
bill: {
four: 'uh',
},
},
},
bar: {
two: 'hiya',
},
}
// @ts-expect-error
const bad: NestedUniqueKeys<Something> = {
foo: {
three: 'hi',
baz: {
one: 'oh',
bill: {
four: 'uh', // duplicated
},
},
},
bar: {
two: 'hiya',
foobar: {
four: 'hey', // duplicated
},
},
}
在最后的公式化中,是否可以推断出完整的键集,从而不需要传入类型参数?
编辑
我尝试了一个初步的解决方案,但这导致了 all 嵌套键被禁止。我猜这是因为当K
被传递到递归的NestedUniqueKeys
时,它被推断为string
?我不知道为什么...
type NestedUniqueKeys<Keys extends string = never> = {
[K in string]: K extends Keys
? never
: string | NestedUniqueKeys<K|Keys>
}
Playground
编辑2
另一个尝试,我不知道为什么这是不允许任何键在嵌套的对象...
type NestedUniqueKeys<Keys extends string = never> =
{ [K in string]: K extends Keys ? never : string } extends infer ThisLevel
? keyof ThisLevel extends string
? ThisLevel & {
[N in string]: N extends Keys ? never : NestedUniqueKeys<keyof ThisLevel|Keys>
}
: never
: never
3条答案
按热度按时间vwhgwdsa1#
我找到了下面这个简单案例的解决方案:
它实际上是相当整洁和小。更一般的解决方案稍微复杂一点:
我从各种来源得到了一些助手类型。这对我来说似乎并不是真的有用,但这是一个有趣的挑战。
8tntrjer2#
这里有另一种方法,我试图保持简单。
我创建了三个泛型类型来进行验证。
GetLeafsPaths
接受一个对象T
,并将所有到leaf的路径计算为字符串。注意,我选择了路径的逆序,这使得以后获取leaf值更容易,因为它只是第一个元素。
ExtractLeafName
取一条路径并提取LeafName
。现在转到主
Validation
类型。这个想法很简单:首先用
GetLeafPaths
得到所有的Paths
,然后在Paths
中的每条路径上MapP
。对于每个路径
P
,我们使用Exclude<Paths, P>
来获取Paths
中所有其他不是P
的路径。我们使用ExtractLeafName
来获取P
和Exclude<Paths, P>
中的叶名称,并将它们与extends
进行比较。如果叶名称位于任何其他路径中,我们返回true
,如果不是,则返回false
。这将生成一个对象类型:
重复的分叶名称具有
true
类型。剩下要做的就是检查这个对象类型中是否有任何
true
值,我们可以使用extends Record<string, false>
来检查。如果任何叶名称重复,
Validate
类型将返回false
。现在我们只需要一个函数,我们可以向它传递一真实的对象。
参数中的一个简单条件类型让我们使用
Validate
来检查T
中是否有重复的叶。这个错误消息并没有什么帮助,但是我已经尽力了。
注:这也可以简单地扩展为不仅验证唯一的leaf,而且验证所有属性。您所需要做的就是修改
GetLeafPaths
以同时构造其他属性的路径:Playground
2skhul333#
请考虑以下示例:
Playground
您可以在注解和my blog中找到解释