Dynamically assign property of object with mixed-type properties in TypeScript

2.2k views Asked by At

Suppose I have an object with some properties that are not all the same type, and I want to assign a dynamic value to a dynamically identified property. This works perfectly fine in JavaScript, but how do I do it in TypeScript, eliminating the not assignable to type 'never' TS(2322) error, ideally without using any?

Sample code (playground here):

interface Film {
    name : string;
    durationInSeconds : number;
}
const myFunction = function(
    film: Film,
    filmPropertyName: keyof Film,
    newValue: string | number
) {
    let value = film[filmPropertyName];
    console.log('Existing value:', value); //value is of type `string | number`, inferred in prior line
    //That means that on read, film[filmPropertyName] is of type `string | number` as expected.
    //However, on write, it's of type `never.`
    film[filmPropertyName] = newValue; //not assignable to type 'never'.ts(2322)
    value = newValue; //works just fine with both sides as `string | number`
    //Checking that types match seems like a sensible requirement but doesn't help:
    if(typeof film[filmPropertyName] === typeof newValue) {
        value = newValue; //works fine
        film[filmPropertyName] = newValue; //same error as above
    }
    //even checking that both are a specific standard type doesn't help:
    if(typeof film[filmPropertyName] === 'string' && typeof newValue === 'string') {
        value = newValue; //works fine
        film[filmPropertyName] = newValue; //same error as above
    }
}

In this tiny example, one might propose removing the type mix e.g. by setting durationInSeconds to a string type and changing the type of the newValue param to string only. While that might be OK in a tiny example like this, it doesn't work on more complex real code where other types are needed.

For someone else coming upon this question, changing the type of the film parameter to any satisfies this error, but may trigger others e.g. if film or its properties are passed on to other functions during the body of this one.

Casting before the assignment, e.g. (film as any)[filmPropertyName] = newValue, makes the error go away and should probably be done within at least the first typechecking conditional, but seems to be giving up the typechecking power of TypeScript - adding any annotations and casts really doesn't seem like "the TypeScript way." What is, in this situation?

1

There are 1 answers

0
WBT On

The solution is to use a double-generic in the type signature like this, keeping everything else the same:

const myFunctionGeneric = function<K extends keyof Film, V extends Film[K]>(
    film: Film,
    filmPropertyName: K,
    newValue: V
) 

Revised playground here.