I'm trying to type a configuration object whose properties have arbitrary names. Each arbitrary key name must have a value which is a typed object with some defined property that has, as a value, one of the key names of the parent object.
Here is what i came up with :
//parent type
type ParentType<KeyNames extends string> = {
[k in KeyNames]: ChildType<KeyNames>;
};
//child type
interface ChildType<KeyNames extends string> {
oneOfKeys : KeyNames
}
The problem is that it's working in the opposite direction i was hoping for.
function testFunc<Keynames extends string>(foo:ParentType<Keynames>){
}
//testFunc is infered as testFunc<"bar" | "baz" | "foo">
//i would like for it to be infered as testFunc<"foo" | "bar" | "bal">
testFunc({
foo: {
oneOfKeys: "bar"
},
bar : {
oneOfKeys: "baz"
},
bal : { //TS is complaining about the "bal" key
oneOfKeys: "foo"
}
})
I wanted the compiler to check that oneOfkeys what one of the key of the parent object, but instead it checks that the parent key is one of the values of oneOfKeys. It's like the generic's inference was working bottom-up when i want it to work top-down.
I have tried various other approaches which don't work at all.
CodePudding user response:
Passing the whole object to ParentType
means the compiler won't erroneously infer the keys wrong:
type ParentType<O> = {
[k in keyof O]: ChildType<O>;
};
//child type
interface ChildType<O> {
oneOfKeys : keyof O
}
function testFunc<O>(foo:ParentType<O>){
}
testFunc({
foo: {
oneOfKeys: "bar"
},
bar : {
oneOfKeys: "baz" // Error
},
bal : {
oneOfKeys: "foo"
}
})
CodePudding user response:
FIrst of all you need to infer each key and value of function argument:
type Value<Prop extends PropertyKey> = { oneOfKeys: Prop }
function testFunc<
ParentKey extends PropertyKey,
Item extends Value<ParentKey>,
Config extends Record<ParentKey, Item>
>(foo: Config) {}
ParentKey
infers all parent keys.
Item
infers all nested objects: {oneOfKeys: string}
Config
infers whole config.
More about infering arguments you can find in my article
Then, you need iterate through infered Config
and find disallowed values:
type Validation<Config extends Record<PropertyKey, Value<PropertyKey>>> = {
[Prop in keyof Config]: Config[Prop]['oneOfKeys'] extends keyof Config ? Config[Prop] : Value<never>
}
type Test = Validation<{
foo: {
oneOfKeys: "bar"
},
bar: {
oneOfKeys: "baz"
},
bal: { //TS is complaining about the "bal" key
oneOfKeys: "foo"
}
}>
Validation
- iterates throught every Config
prop and checks whether Config[Prop]['oneOfKeys']
extends any parent key. If yes - leavs value as it is, otherwise replace value with never
.
WHole code:
type Validation<Config extends Record<PropertyKey, Value<PropertyKey>>> = {
[Prop in keyof Config]: Config[Prop]['oneOfKeys'] extends keyof Config ? Config[Prop] : Value<never>
}
type Test = Validation<{
foo: {
oneOfKeys: "bar"
},
bar: {
oneOfKeys: "baz"
},
bal: {
oneOfKeys: "foo"
}
}>
type Value<Prop extends PropertyKey> = { oneOfKeys: Prop }
function testFunc<
ParentKey extends PropertyKey,
Item extends Value<ParentKey>,
Config extends Record<ParentKey, Item>
>(foo: Validation<Config>) {}
testFunc({
foo: {
oneOfKeys: "bar"
},
bar: {
oneOfKeys: "baz" // error
},
bal: {
oneOfKeys: "foo"
}
})