lemme
lemme

Reputation: 63

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.

Upvotes: 3

Views: 4961

Answers (3)

ABOS
ABOS

Reputation: 3821

Can you try

const updateObjectProperty = <T extends keyof MyObject>(key: T, value: MyObject[T]) => {
    object[key] = value
}

Upvotes: 0

Ruan Mendes
Ruan Mendes

Reputation: 92334

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.

See a live example

function updateObjectProperty<T extends keyof MyObject>(key: T, value: MyObject[T]) {
    object[key] = value;
}

Notice the squigglies showing you invalid parameters passed.

enter image description here

Upvotes: 2

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250366

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

Playground Link

The reason your version does not work is that you could pass in uncorrelated key and value:

updateObjectProperty("key2", "") // key2 now has a string

Playground Link

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

Playground Link

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

Playground Link

Upvotes: 3

Related Questions