Elliot Bonneville
Elliot Bonneville

Reputation: 53311

Union type as key in interface?

Is it possible to use a union type as a key in an interface? For example, I want to do something like this:

interface IMargin {
  [key in 'foo' | 'bar']: boolean;
}

But I'm getting this error:

A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1169)

Is there any way around this?

The use case is turning an array of values into an interface:

const possibleTypes = ['foo', 'bar'];
interface Types {
    foo?: boolean;
    bar?: boolean;
}

Upvotes: 46

Views: 44482

Answers (6)

ps2goat
ps2goat

Reputation: 8475

This builds on the other answers, and adds an answer to a question about selectively specifying languages.

I prefer the use of Record over the index specification ([key of/in]) as it's a bit easier to read and understand (Record<property name type, value type>).

The additional question:

did you get a workaround to get just one of the languages as a single key, instead of multiple possible keys?

In this case, use the Partial helper. Wrapping anything with Partial<other type> makes all the properties optional (able to be undefined).

Here's a playground example.

export type Language = 'EN' | 'DE' | 'IT';

export type LanguageMap = Partial<Record<Language, string[]>>;

export class Document {
    public generic: string;
    public languages: LanguageMap;

    constructor(generic: string, langMap: LanguageMap) {
        this.generic = generic;
        this.languages = langMap;
    }
}

const x = new Document(
    'test',
    {
        'EN': ['English', 'British English', 'Australian English', 'US English']
    }
);

const y = new Document(
    'test',
    // this can be empty!
    {
    }
);

Upvotes: 1

apollov
apollov

Reputation: 147

const possibleTypes = ['foo', 'bar'] as const;
type Types = Record<typeof possibleTypes[number], boolean>

Saying as const we declare that the array is immutable and not just Array<string>. possibleTypes[number] is needed to filter out array methods, like length from the type.

Upvotes: 0

Ranjan Kumar
Ranjan Kumar

Reputation: 349

convert possible types to enum convert interface to type since An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead

enum possibleTypes { 'foo', 'bar' };
type Types {
    [key in keyof typeof possibleTypes]: boolean
}

Upvotes: 2

Pablo Oliva
Pablo Oliva

Reputation: 763

This is not necessarily an answer, but I think this may interest others.

You can use a Union Type as a key in a sub-property of an Interface.

export type Language = 'EN' | 'DE' | 'IT';

export interface Document {
  generic: string;
  languages: {
    [key in Language]: string[];
  }
}

Upvotes: 19

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249636

Interfaces do not support mapped types (which is what you are looing for here). You can use a type alias, for most uses it should work the same (not all cases but if the types are known you can implement the type alias just as you would the interface).

const possibleTypes = (<T extends string[]>(...o: T) => o)('foo', 'bar');
type Types = Record<typeof possibleTypes[number], boolean>

The mapped type Record should work will here

Upvotes: 6

Dmitriy
Dmitriy

Reputation: 2822

You can use an object type instead of an interface, which are mostly interchangeable:

type IMargin = {
    [key in 'foo' | 'bar']: boolean;
}

Upvotes: 60

Related Questions