I have a requirement for an object type to not duplicate keys across nested objects. For example, if foo.bar
contains the key hello
then foo.baz
cannot contain that key. Is there any way to enforce this at the type level?
One simplified formulation might be something like the following:
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
},
}
So a simpler step might be, how could NestedUniqueKeys
be formulated for a single level of nesting?
Then, how to extend it to arbitrary nestings?
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
},
},
}
And in the final formulation, could it be made to infer the full set of keys so no type parameter needs to be passed in?
Edit
I tried an initial sketch of something approaching the solution, but this results in all nested keys being forbidden. I guess this is because K
is inferred to be string
when it's passed into the recursive NestedUniqueKeys
? I'm not sure why...
type NestedUniqueKeys<Keys extends string = never> = {
[K in string]: K extends Keys
? never
: string | NestedUniqueKeys<K|Keys>
}
Edit 2
Another attempt, I'm not sure why this isn't allowing any keys in the nested objects...
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