Reputation: 21
I wrote a function in typescript 4.0.5 which runs through an object and executes a given callback for each property, similar to array.forEach (see below).
If noImplicitAny: true
is set in tsconfig, the function's typing only seems to work if all properties share the same type.
export function forEachKey<T extends Object>(obj: T, cb: (key: Extract<keyof T, string>, value: T[keyof T]) => void): void {
for (const k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
cb(k, obj[k]);
}
}
}
With the following example there are no errors:
interface TestNumber {
a: number;
b: number;
}
const t1Nr: TestNumber = { a: 1, b: 2 };
const t2Nr: TestNumber = { a: 3, b: 4 };
forEachKey(t1Nr, (key, value) => {
t2Nr[key] = value;
});
But as soon as there are properties with different type:
interface TestMixed {
a: number;
b: string;
}
const t1: TestMixed = { a: 1, b: '2' };
const t2: TestMixed = { a: 1, b: '2' };
forEachKey(t1, (key, value) => {
t2[key] = value; // error
});
I get the error:
TS2322: Type 'string | number' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'.
As far as I see I have to declare the type of value
as T[key]
but I couldn't found a way to do that.
Does anyone know how to fix the typing? Thanks in advance
Edit:
After Aleksey's answer was working with this simple example I tried to convert the example into my current implementation. There we have a class like the following:
class TestMixed2Class implements TestMixed {
constructor (public a: number = 1, public b: string = '2', public c: number = 3) {}
setParams(params: TestMixed) {
forEachKey(params, (key, value) => this[key] = value); // error
}
}
It results in the error
TS2322: Type 'TestMixed[K]' is not assignable to type 'this[K]'.
Type 'TestMixed' is not assignable to type 'this'.
'this' could be instantiated with an arbitrary type which could be unrelated to 'TestMixed'.
Type 'TestMixed[K]' is not assignable to type 'never'.
Type 'string | number' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
Is there also a way to achieve a valid typing here?
Upvotes: 2
Views: 450
Reputation: 37986
You can introduce generic type parameter in inner function for key
, so Typescript will be able to match key
and value
:
function forEachKey<T extends object>(obj: T, cb: <K extends keyof T>(key: K, value: T[K]) => void): void {
for (const k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
cb(k, obj[k]);
}
}
}
Upvotes: 2