Typescript: Index signature pattern with specific keys in an interface

Created on 12 Apr 2017  路  11Comments  路  Source: microsoft/TypeScript

Hi
I am trying to create an interface that will allow only specific strings as keys by using index signature but unable to do so. I am hoping it can be added in the next version.

Here is an example:

type RestrictedStyleAttribute = "color" | "background-color" | "font-weight";

interface IRestrictedStyle {
    [index: RestrictedStyleAttribute] : string
}

When I do what is above, I get An index signature parameter type must be 'string' or 'number'.
I don't want to use the type string for the index since I don't want any string to be a key in an object that implements IRestrictedStyle interface.
Since the type RestrictedStyleAttribute is a sub type of string, I think, it should be allowed and necessary checks should be made on compile.

Duplicate

Most helpful comment

Version: 2.3.0-dev.20170412

It seems type system does not work on index signature.

The following code will cause the same error:

type Key = string;
interface Dictionary<T> {
    [key: Key]: T;
}

type Index = number;
interface List<T> {
    [index: Index]: T;
    length: number;
}

Edit:

I think this may be what @nozer want:

type RestrictedStyleAttribute = "color" | "background-color" | "font-weight";
type IRestrictedStyle = {
    [T in RestrictedStyleAttribute]: string;
}

All 11 comments

Version: 2.3.0-dev.20170412

It seems type system does not work on index signature.

The following code will cause the same error:

type Key = string;
interface Dictionary<T> {
    [key: Key]: T;
}

type Index = number;
interface List<T> {
    [index: Index]: T;
    length: number;
}

Edit:

I think this may be what @nozer want:

type RestrictedStyleAttribute = "color" | "background-color" | "font-weight";
type IRestrictedStyle = {
    [T in RestrictedStyleAttribute]: string;
}

Yes, it seems to expect either string or number explicitly without doing any inference about the index type.

That works to some extent. If I don't use optional for the key, it requires all the RestrictedStyleAttribute types to exist in the implementing object. If I use optional, then it accepts empty object as valid too. I do not want empty object for my case.

type IRestrictedStyle = {
    [T in RestrictedStyleAttribute]?: string; // makes empty object valid for this type 
}

type IRestrictedStyle = {
    [T in RestrictedStyleAttribute]: string; // requires keys for all RestrictedStyleAttribute
}

I think you can do string | void

let x:IRestrictedStyle = {}; is still valid when I do string | void. Basically, I would like implementing object to have at least one of the keys.

@nozer

type A = { a: string };
type B = { b: string };
type C = { c: string };

type Result = (A | B | C) & Partial<A & B & C>;

// this will cause error
const result1: Result = {};

// others are OK
const result2: Result = {
    a: 'a',
};

const result3: Result = {
    b: 'b',
};

const result4: Result = {
    a: 'a',
    b: 'b',
    c: 'c',
};

You'd likely need union types to do that (untested):

type IRestrictedStyle = {
  "color": string,
  [T in RestrictedStyleAttribute]: string | undefined,
} | {
  "background-color": string,
  [T in RestrictedStyleAttribute]: string | undefined,
} | {
  "font-weight": string,
  [T in RestrictedStyleAttribute]: string | undefined,
}

etc. I don't think there's a way to say that the object as at least one of those keys w/o saying it explicitly.

@aaronjensen @ikatyang
That is a bit too long. I could do this for example:

interface ColorAttr {
    color: string
}
interface BgColorAttr {
    'background-color': string
}
interface FontWeightAttr {
    'font-weight': string
}

type IRestrictedStyle = ColorAttr | BgColorAttr | FontWeightAttr;

I am not saying there is not any way to do it. I am merely suggesting a simpler way to express it in the next version.

@ikatyang 's last answer is pretty succinct though; still, a way to do it in the index signature format would be much simpler.

@nozer

interface ColorAttr {
    color: string
}
interface BgColorAttr {
    'background-color': string
}
interface FontWeightAttr {
    'font-weight': string
}

type IRestrictedStyle = ColorAttr | BgColorAttr | FontWeightAttr;

const style: IRestrictedStyle = {
    color: 'color',
    'background-color': 123, // this wont be an error, since it just has to be one of those
};

@ikatyang Waow, that is interesting. Thanks for pointing that out.

Was this page helpful?
0 / 5 - 0 ratings