6

I'm trying to create a type to describe an ES6 Proxy object where I will know the types for a few keys, and the rest of the keys will be generic with a callback as a value and I won't know their names until runtime.

However, if I try something like this:

interface MyCallback {
  (): void;
}

interface MyType {
    myKey1: number;
    [key: string]: MyCallBack;
}

I get errors like:

[ts] Property 'myKey1' of type 'number' is not assignable to string index type 'MyCallback'.

If I add [key: string]: number, I get the error Duplicate string index signature.

If I overload it so it's like number | MyCallback, I get this error if I try to call a callback on a MyType instance:

[ts] Cannot invoke an expression whose type lacks a call signature. Type 'number | MyCallback' has no compatible call signatures.

Is it possible to have a type like I'm trying to create in TypeScript?

empyrical
  • 362
  • 2
  • 5
  • 16
  • Possible duplicate of [How to type check this object without using {} or any?](https://stackoverflow.com/questions/50198477/how-to-type-check-this-object-without-using-or-any) – jcalz May 21 '18 at 18:12

3 Answers3

2

the answer is sort of. You can accomplish this with intersection types:

interface MyType {
    myKey1: number;
}

interface MyCallBack {
    (): void;
}

interface GenericCallbackType {
    [key: string]: MyCallBack;
}

type CombinedType = MyType & GenericCallbackType;

const obj: CombinedType = {
    myKey1: 8,
    anyString: () => {}
}
bryan60
  • 28,215
  • 4
  • 48
  • 65
  • 1
    Thank you! This works for me if I make it an intersection type, like `type CombinedType = MyType & GenericCallbackType;` instead. – empyrical May 21 '18 at 18:34
  • whoops, actually meant to put intersection, my bad, good catch! – bryan60 May 21 '18 at 18:36
  • Is a specific setting required for this? I get the following on 2.8.3: `Property 'myKey1' is incompatible with index signature. Type 'number' is not assignable to type 'MyCallBack'.` – Clint May 21 '18 at 19:04
  • @empyrical, would either you care to comment on your TS version? Docs and [typescript issues](https://github.com/Microsoft/TypeScript/issues/17867) say that this should not work (and it doesn't in 2.8.3). – Clint May 22 '18 at 01:54
  • I'm using version 2.6.2 - I haven't tried other versions yet, but VS Code hasn't been giving me any complaints yet. I notice one of the commenters mentions issues with assignments, but I am just accessing and have no need for assignment in my use case. – empyrical May 22 '18 at 02:58
  • not working for me also. is this answer still valid? – niryo Apr 16 '20 at 14:36
  • In typescript 4.2.4 you'll get `Type 'number' is not assignable to type 'MyCallBack'.ts(2322)`. – kernel May 06 '21 at 08:18
2

As noted in comments the accepted answer doesn't work with assignments, resulting in a Property 'myKey1' is incompatible with index signature error. To work with assignments we can utilize @jcalz's answer here:

interface MyCallback {
  (): void
}

interface MyType {
  myKey1: number
}

const asCombinedType = <C>(
  res: C & MyType & Record<Exclude<keyof C, keyof MyType>, MyCallback>
): C => res

const obj = asCombinedType({
  anyKey: () => { /* ...*/ },
  myKey1: 12
})

Admittedly a bit convoluted but it gets the job done.

Aleksi
  • 4,483
  • 33
  • 45
2

the accepted answer didn't work for me, this snippet works: Playground Link

interface MyType {
    myKey1: number;
}

interface GenericCallbackType {
    [key: string]: () => void;
}

type CombinedType = MyType | GenericCallbackType;

const obj: CombinedType = {
    myKey1: 8,
    anyString: () => {}
}
niryo
  • 1,275
  • 4
  • 15
  • 26