3

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.

3 Answers 3

3

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

Sign up to request clarification or add additional context in comments.

Comments

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.

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

Comments

0

Can you try

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.