2

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);
}
nick zoum
  • 7,216
  • 7
  • 36
  • 80

2 Answers2

2

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

Guerric P
  • 30,447
  • 6
  • 48
  • 86
1

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
}
Sergii Stotskyi
  • 5,134
  • 1
  • 22
  • 21
  • Your answer uses Typescript. I'm using JavaScript. – nick zoum Mar 27 '20 at 20:11
  • what you need to do is to extract typescript to d.ts :) it's easy. I can do this for you – Sergii Stotskyi Mar 27 '20 at 20:12
  • Changed `export function forEach(obj: T, callbackfn: (value: K, key: string, object: T) => void): void;` to `export function forEach(obj: T, callbackfn: (value: T[keyof T], key: string, object: T) => void): void;` (Don't see the point of using `key: keyof T` if it's always going to be a `string`). – nick zoum Mar 27 '20 at 20:14
  • it's not always a string! look at typescript literal types! https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types – Sergii Stotskyi Mar 27 '20 at 20:16
  • I'm using `for (var key in obj)` the value of `key` will always be a `string`. – nick zoum Mar 27 '20 at 20:17
  • @nickzoum not always, especially in typescript ;) it can be a stricter type. As I said look at literal types. – Sergii Stotskyi Mar 27 '20 at 20:18
  • I'm using `for (var key in obj)` in `JavaScript` therefore it will always be a `string`. I'm not using any typescript just `.d.ts` for better documentation. – nick zoum Mar 27 '20 at 20:19