TypeScript: Can't use indexed/mapped type signature in interfaces?

TypeScript Version: 2.1.5

What I’d like to do is effectively extend from a record type and then add a few more custom properties. Since type aliases cannot by extended, I tried solving the problem with an indexed type signature in the interface. Unfortunately, this doesn’t compile. I also can’t add extra properties to a mapped type, so it seems I may be stuck. Is there a way around this problem?

Code

type Names = 'foo' | 'bar' | 'baz';

interface Thing1 {
  readonly [T in Names]?: string;
}

interface Thing2 {
  readonly [T in Names]?: string;
  extra?: number;
}

type Thing3 = Readonly<Partial<Record<Names, string>>>;

type Thing4 = {
  readonly [T in Names]?: string;
};

type Thing5 = {
  readonly [T in Names]?: string;
  extra?: number;
};

Expected behavior: I would expect all of these to compile.

Actual behavior: Thing1 and Thing2 fail to compile due to the indexed type signature. Thing5 fails to compile due to the extra property.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 57
  • Comments: 18 (6 by maintainers)

Most upvoted comments

+1 Hope we can use ‘in’ operator in index signature while declaring interfaces not only types. We really need a mapped index sometimes. The inconsistency between interface and type looks confusing. At present, I have to change an interface declaring to type which is surrounded by vast interfaces declaring.

Is there any movement on this issue? It seems to be something that is extremely useful, and quite a common use case.

If there is no plan to support something like:

enum Types { Foo, Bar }
interface Things {
  [k in Types]: boolean;
  quu: string;
}

then is there a suggested alternative pattern that achieves the same goal – namely being able to define an object shape where the key may be any of a list of acceptable values?

@jas99 Not sure this will solve uses that rely on generic parameters, but I think the original code will work when #26797 gets merged.

This proposal fells like natural extension of mapped types functionality. I guess many people try this exact proposed syntax to then see that it doesn’t work.

In my case it would be very useful for situation when we want to extend 3rd party interface with properties that can be “computed” by mapped type. In theory we can achieve the result by some workarounds like writing interface that extends mapped type alias: image

but this would be much nicer if I could write [P in Names] : string; inside interface.

I guess this issue is not a big priority for TS team because from what I can see there is nothing fundamentally new that you can’t do today. For instance the examples from OP could be written as:

type Names = 'foo' | 'bar' | 'baz';

type Temp = { readonly [T in Names]?: string };

interface Thing1 extends Temp {}

interface Thing2 extends Temp {
  extra?: number;
}

type Thing3 = Readonly<Partial<Record<Names, string>>>;

type Thing4 = { readonly [T in Names]?: string };

type Thing5 = {
  extra?: number;
} & Temp;

There is just this confusion for new users about where you can use mapped type syntax and where not and IMO this confusion itself is worth to consider fixing it.

Note that this isn’t completely a duplicate of #26797, since that one would not have really supported mapped types (index signatures don’t give you a handle on the elements in the set of keys). This issue seems to be the canonical one for “why can’t we add extra properties next to mapped types” which isn’t quite the same as “why can’t we have arbitrary index signatures”.