TypeScript: Quick fix for 'unions can't be used in index signatures, use a mapped object type instead'

The following code:

type K = "foo" | "bar";

interface SomeType {
    [prop: K]: any;
}

Gives this error message:

An index signature parameter type cannot be a union type. Consider using a mapped object type instead.

Nobody knows what mapped object types are, so let’s give them a quick fix that

  • Switches the index signature to a mapped type
  • Moves other members to a separate object type that gets combined with an intersection type
  • Changes the containing object type to a type alias if the containing object type is an interface
  • Intersects with all the extends clauses if the containing object type is an interface and has any extends clauses

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 291
  • Comments: 37 (4 by maintainers)

Most upvoted comments

You can do this:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Though Bar has no index signature (i.e., you can’t then do (obj as Bar)[value as Foo]).

Edit: Though if you could make the caveat a non-issue, I’d be eternally grateful!

Just curious why type works, but interface doesn’t. Can someone explain, please? What’s the reason for such a limitation (or a feature?) of interface.

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any}; // ok
interface Baz {[key in Foo]: any} // =>

// 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)
// A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)
// 'Foo' only refers to a type, but is being used as a value here.ts(2693)

You can do this:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Though Bar has no index signature (i.e., you can’t then do (obj as Bar)[value as Foo]).

Edit: Though if you could make the caveat a non-issue, I’d be eternally grateful!

Use Record instead!

type Foo = 'a' | 'b'
type Bar = Record<Foo, any>

@maicWorkGithub here you go:

const user = createRequestTypes('USER')
console.log(user.REQUEST) 

function createRequestTypes(base:string):requestTypes {
  const result : requestTypes    = {}
  const arr    : requestStatus[] = ['REQUEST', 'SUCCESS', 'FAILURE']  
  
  return arr.reduce((acc, type) => {
    acc[type] = `${base}_${type}`
    return acc
  }, result)
}


type requestStatus = 'REQUEST' | 'SUCCESS' | 'FAILURE'
type requestTypes = { [key in requestStatus]?: string }

its even better when using Partial

type A = 'x' | 'y' | 'z';
type M = Partial<{
    [key in A]: boolean
}>;

To add one more example of this using a class…

class Foo {
   a: string;
   b: string;
}

type Bar = {[key in keyof Foo]: any};

Why exactly can’t an index signature use an enum type? The mapped type almost does what I want, but then TypeScript expects every string from the enum to exist as a defined key. I don’t actually want to assert that every key exists, more that if any keys do exist, they must live in the enum.

For example for the type:

type MyType = {
  [Key: 'foo' | 'bar' | 'zip']: number;
};

This should satisfy:

const x: MyType = {
  foo: 1,
  zip: 2
};

While I could just set the other keys undefined for a mapped type, I prefer to make the keys optional, but if they’re present, the value cannot be undefined. If I make the mapped type values optional the code works but the types are less strong.

This was an amazing auto-fix to discover. Thank you for implementing it! 😃

“Partial” can be used on Records too:

type Foo = 'a' | 'b';
let foo1: Record<Foo, number> = { a: 1, b: 2 };
let foo2: Partial<Record<Foo, number>> = { a: 1 };

I find myself unwittingly visiting this GitHub page every month or so.

My latest one is a real simple one:

interface ObjectLiteral {
    [key: string | number]: any
}
export const mapToObjectLiteral = (map: Map<string|number, any>) =>
    Array.from(map).reduce((objLit, [key, value]) => {
        objLit[key] = value
        return objLit
    }, {} as ObjectLiteral)

image

I can scroll up and figure out a workaround, but just wanted to provide feedback that this issue happens frequently in day to day work in slightly different scenarios.

its even better when using Partial

type A = 'x' | 'y' | 'z';
type M = Partial<{
    [key in A]: boolean
}>;

Thanks! Useful when you need to define a type that partially matches a dictionary

For typescript 3.5, it seems like I have to do this:

export interface DataTableState {
  columnStats: {[key in keyof DataTable]?:{}}
}

Is this the best way to do this?

@mvasin FWIW, this appears to achieve the same result, but I agree entirely that it should be a feature of interfaces just as it is on types.

type Foo = 'a' | 'b';

type Bar = {
  [key in Foo]: any
}

interface A extends Bar { }

class Wol implements A{
  a: any;
  b: any;
}

Moves other members to a separate object type that gets combined with an intersection type

what should we do if containing object type is an class?
I can only imagine that it is an interface

so what should follow code do after quickfix?

type K = "1" | "2"

class SomeType {
    a = 1;
    [prop: K]: any;
}

@b4dnewz fair enough,

Perhaps a simpler option you might be able to use is:

export type BitwiseCondition =
  | { or: number }
  | { xor: number }
  | { and: number }

That’s about the closest you’ll get without too much duplication

@b4dnewz, if you only want 1 property, why not do it like this?

export enum BitwiseOperator {
  and = "and",
  or = "or",
  xor = "xor",
}

export type BitwiseCondition = {
  operator: BitwiseOperator;
  value: number;
}

@ThaJay We won’t backport this feature, try setting up a newer version.