nick zoum
nick zoum

Reputation: 7325

Nested generics in typings function

This question is about .d.ts as declarations for .js files.

I'm trying to declare a function that has a generic inside a generic but I can't seem to get it right. The function is essentially an Array#forEach function for objects ({[key: string]: E}).

I tried the following, but VSCode intellisense doesn't seem to comprehend the types of the property values of the object.

Typings File:

export module Util {
    export function forEach<K, T extends { [key: string]: K }>(obj: T, callbackfn: (value: K, key: string, object: T) => void): void;
}

JavaScript Call:

if (undefined) var { Util } = require("./globals");
/** @type {{ [key:string]: number }} */
var obj = {};
Util.forEach(obj, function (value, key, object) { });

Source code of forEach:

function forEach(obj, callbackfn) {
    if (!(obj instanceof Object)) throw Error("Util.forEach called on non object");
    for (var key in obj) callbackfn.call(obj, obj[key], key, obj);
}

Upvotes: 2

Views: 307

Answers (2)

Sergii Stotskyi
Sergii Stotskyi

Reputation: 5400

What you actually want to do is this:

export function forEach<T extends object>(obj: T, callbackfn: (value: T[keyof T], key: keyof T, object: T) => void): void {
  if (!(obj instanceof Object)) throw Error("Util.forEach called on non object");
  for (var key in obj) callbackfn.call(obj, obj[key], key, obj);
}

const a = {
  test: 1,
  test2: 2,
  test3: 'me'
};

forEach(a, (value, key, obj) => {})

You need only 1 generic parameter! You can infer the rest of types from it enter image description here

This works even with stricter types:

enter image description here

Update: add d.ts file:

export module Util {
  export function forEach<T extends object>(obj: T, callbackfn: (value: T[keyof T], key: keyof T, object: T) => void): void
}

Upvotes: 1

Guerric P
Guerric P

Reputation: 31835

I have no problems with your code. As you can see, the object properties types show correctly, when imported in a JS file and TS file as well:

enter image description here

Tested with Visual Studio Code 1.43.1 and Node.js 13.11.0

I have uploaded the code here: https://github.com/Guerric-P/typescript-test

Edit:

Now I understand your actual question, here is how you do it:

export module Util {
    export function forEach<T, T1 extends keyof T, T2 extends T[keyof T]>(obj: T, callbackfn: (value: T2, key: T1, object: T) => void): void;
}

Which gives the following:

enter image description here enter image description here

If you want more details on why the key type is string | number instead of string, please check this question: Keyof inferring string | number when key is only a string

Upvotes: 2

Related Questions