vgl
vgl

Reputation: 21

Typing of callback receiving a key-value-pair of object in typescript

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

Answers (1)

Aleksey L.
Aleksey L.

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]);
    }
  }
}

Playground

Upvotes: 2

Related Questions