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?
The solution is to use a double-generic in the type signature like this, keeping everything else the same:
Revised playground here.