I have a function that will update the field of an object like so:
const obj = {a: 1, b:2};
const changeObj = (field, value) => obj[field] = value;
This is obviously simplified, but I'm trying to make this typed properly based on the field property that is passed in. This is as close as I've gotten:
interface ITest {
name: string;
anotherProp: number;
lastProp: IOtherInterface;
}
const obj: ITest = {name: 'test', anotherProp: 1, lastProp: {a: 'b'}};
const changeObj = (field: keyof ITest, val: ITest[keyof ITest]) => obj[field] = val;
Which gets me most of the way there, but technically val can have a value type of string, number, or IOtherInterface. Is there a way to tell the function that if I'm passing in name and val can only be a string in that case?
CodePudding user response:
In order for this to work you want changeObj to be a generic function:
const changeObj = <K extends keyof ITest>(
field: K, val: ITest[K]
) => obj[field] = val; // okay
The field parameter is of type K, a type parameter that's constrained to keyof ITest. When you call changeObj(), the compiler will infer K based on what's passed into field. Then the val parameter will be checked against ITest[K], the type of the value you get if you index into ITest with a key of type K:
changeObj("name", "hey") // okay
changeObj("name", 123) // error, number not a string
This makes it much less likely that you'll pass the wrong thing into changeObj. But it doesn't completely prevent it; if field is of a union type, then the compiler will also accept a val of the analogous union type, which could cause problems:
changeObj(Math.random() < 0.99 ? "name" : "anotherProp", 123); // okay?
// uh oh, very good chance of a problem
This sort of unsoundness is just part of TypeScript; the trouble situation doesn't crop up frequently enough to be addressed in the language itself.
