I would to know how to subtract identical object values in typescript using Generics and type-safe, based on: subtracting identical object values javascript
const subtractObjects = <T extends Record<String, number>>(objA: T, objB: T): T =>
Object.keys(objA).reduce((a, k) => {
a[k] = objA[k] - objB[k];
return a;
}, {});
I receive the error:
Type '{}' is not assignable to type 'T'.
'{}' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Record<string, number>'.
CodePudding user response:
When you have a generic type parameter T
constrained to Record<string, number>
, it doesn't necessarily mean that every property of T
will have a value type that's exactly number
. It could well be something that extends number
, such as a numeric literal type like 123
or a union of such types like 0 | 1
. (See
Why can't I return a generic 'T' to satisfy a Partial<T>? for a related discussion.) For example:
type Money = {
denomination: 1 | 5 | 10 | 20 | 50 | 100
}
const twenty: Money = { denomination: 20 };
const one: Money = { denomination: 1 };
const nineteen = subtractObjects<Money>(twenty, one);
/* const nineteen: Money */
console.log(nineteen.denomination) // 1 | 5 | 10 | 20 | 50 | 100 at compile time
// but 19 at runtime
Here, the type Money
has a single known property denomination
whose value must be one of a particular set of numbers. And since Money extends Record<string, number>
, you are allowed to pass two Money
s into subtractObjects()
. And according to the call signature for subtractObjects()
, that means a Money
will come out. The compiler says that nineteen
is valid Money
. But if you check at runtime, its denomination
property is not one of the allowed values. You've accidentally produced counterfeit currency.
If it's not clear from that code example why counterfeit currency is a problem, then note that once you have a situation where the compiler is mistaken about the type of a value, it can lead to runtime errors:
function moneyName(m: Money) {
return {
1: "One",
5: "Five",
10: "Ten",
20: "Twenty",
50: "Fifty",
100: "One Hundred"
}[m.denomination]
}
console.log(moneyName(one).toUpperCase()) // ONE
console.log(moneyName(twenty).toUpperCase()) // TWENTY
console.log(moneyName(nineteen).toUpperCase()) //