Reputation: 13
If I were to create a simple object such as the type below, I want to create a function that takes the object, a valid key of the object and a valid value based on the type of the value of the object's key.
export interface MyType {
prop1: string;
prop2?: number;
prop3?: string[];
}
I found I could easily type the update function to only allow valid properties for the type using the keyOf MyType method.
propertyName: keyOf MyType;
I also found I can type the value by accessing by accessing the type at the key. The code below should be a string.
propertyValue: MyType["prop1"];
However, I am having trouble typing propertyValue using all possible keys, prop1, prop2, and prop3.
export interface MyType {
prop1: string;
prop2?: number;
prop3?: string[];
}
const instanceOfMyType: MyType = {
prop1: "hello"
}`
const updatePropOfMyType = (thingBeingUpdated: MyType,
propertyName: keyof MyType,
propertyValue: MyType[typeof propertyName]): MyType
=>{
const updatedType = _.cloneDeep(thingBeingUpdated);
updatedType[propertyName] = propertyValue;
return updatedType;
};
const updatedInstanceOfMyType = updatePropOfMyType(instanceOfMyType, "prop2", 123);
console.log(updatedInstanceOfMyType)
// Expecting output {prop1: 'hello", prop2: 123}`
similarly
const anotherUpdatedInstanceOfMyType = updatePropOfMyType(instanceOfMyType, "prop2", "hi");
//should give me a type error since prop2 is a number not a string;
Instead I have a type error "Type 'string | number | string[] | undefined' is not assignable to type 'never'. Type 'undefined' is not assignable to type 'never'." at the updatedType[propertyName] = propertyValue;
This appears to be due to how typescript is concatenating all possible types for the properties of MyType rather than filtering the type for the currently set value of PropertyName.
I am just trying to see if there is a way to dynamically enforce the type of the propertyValue to be the same as a given property based on the value of the currently set propertyName;
Upvotes: 1
Views: 133
Reputation: 327624
If you want the compiler to keep track of the relationship between the particular argument passed in for propertyName
and the type of propertyValue
, you should make updatePropOfMyType
generic in the type K
of propertyName
constrained to keyof MyType
, as follows:
const updatePropOfMyType = <K extends keyof MyType>(thingBeingUpdated: MyType,
propertyName: K,
propertyValue: MyType[K]): MyType => {
const updatedType: MyType = structuredClone(thingBeingUpdated)
updatedType[propertyName] = propertyValue;
return updatedType;
};
This will give you the call behavior you wanted:
const updatedInstanceOfMyType =
updatePropOfMyType(instanceOfMyType, "prop2", 123); // okay
console.log(updatedInstanceOfMyType);
const anotherUpdatedInstanceOfMyType =
updatePropOfMyType(instanceOfMyType, "prop2", "hi"); // error
// 'string' is not assignable to 'number' ------> ~~~~
Your original version doesn't work because the type of propertyName
is the full union type keyof MyType
and there is no "currently set" type the compiler can perceive, and so typeof propertyName
is also the full union. By making the type of propertyName
generic, you allow the compiler to narrow propertyName
from keyof MyType
to some specific literal type and then use it to narrow propertyValue
as well.
Upvotes: 1