'Create generic TypeScript function that will assign a value to object property
I want to create a simple function that will take key of a specific object and value for the respective key and assign the new value to the object. Something like this:
interface MyObject {
key1: string
key2: number
}
const object: MyObject = {
key1: 'abc',
key2: 100
}
const updateObjectProperty = (key: keyof MyObject, value: MyObject[keyof MyObject]) => {
object[key] = value
}
But in this case - it's not working. TypeScript complier shows an error:
Type 'string | number' is not assignable to type 'never'.
I feel like maybe some generics could potentially resolve the problem here, but I'm struggling to figure out how.
Solution 1:[1]
If you tie the parameters together using a generic type parameter, it will compile:
const updateObjectProperty = <K extends keyof MyObject>(key: K, value: MyObject[K]) => {
object[key] = value
}
updateObjectProperty("key1", "") //ok
updateObjectProperty("key2", "") // err
updateObjectProperty("key2", 2) // ok
The reason your version does not work is that you could pass in uncorrelated key and value:
updateObjectProperty("key2", "") // key2 now has a string
Now our new version is not 100% safe either, you could call it with key union, but that is less common:
let k: keyof MyObject = Math.random() > 0.5 ? "key1" : "key2"
let v: MyObject[keyof MyObject] = Math.random() > 0.5 ? 0 : "key2"
updateObjectProperty(k, v); // fails when key and value are mssmatched
You could do the same thing that TS does, and type value as an intersection of possible property values, and this will catch this loophole too, but that would require an assertion in the implementation again:
type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = {
[P in TKey]: (k: T[P])=>void
} [TKey] extends ((k: infer I)=>void) ? I : never
const updateObjectProperty = <K extends keyof MyObject>(key: K, value: ValueIntersectionByKeyUnion<MyObject, K>) => {
object[key] = value as MyObject[K];
}
let k: keyof MyObject = Math.random() > 0.5 ? "key1" : "key2"
let v: MyObject[keyof MyObject] = Math.random() > 0.5 ? 0 : "key2"
updateObjectProperty(k, v); //err
updateObjectProperty("key1", "") //ok
updateObjectProperty("key2", "") // err
updateObjectProperty("key2", 2) // ok
Solution 2:[2]
The problem is that you are not specifying a relationship between key and value. You are saying that key can be any property in MyObj and that value can be the type of any of that object's properties.
If you define a generic T as keyof MyObject and use that to define both parameters, you will get the behavior you are looking for.
function updateObjectProperty<T extends keyof MyObject>(key: T, value: MyObject[T]) {
object[key] = value;
}
Notice the squigglies showing you invalid parameters passed.
Solution 3:[3]
Can you try
const updateObjectProperty = <T extends keyof MyObject>(key: T, value: MyObject[T]) => {
object[key] = value
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Titian Cernicova-Dragomir |
| Solution 2 | Juan Mendes |
| Solution 3 | Kai - Kazuya Ito |

