Typescript: Creating types from values in array

Created on 22 Oct 2018  ·  16Comments  ·  Source: microsoft/TypeScript


TypeScript Version: 3.0.3


Search Terms: Type based on values in array

Is there a current or planned feature to create a type from strings in an array?

Code

const values = ['A', 'B']
type Foo = OneOf<values> // Is there a way of doing this?

const v1: Foo = 'A' // This should work
const v2: Foo = 'D' // This should give me an error since 'D' doesn't exist in values

Similar to how keyof works:

const values = {
  A: 'A',
  B: 'B'
}
type Foo = keyof typeof values
const v1: Foo = 'A'
const v2: Foo = 'D' // Type '"D"' is not assignable to type '"A" | "B"'

Related Issues: https://github.com/Microsoft/TypeScript/issues/20965

Link to playground http://www.typescriptlang.org/play/#src=let%20vals1%20%3D%20%5B'A'%2C%20'B'%5D%0D%0Atype%20Foo1%20%3D%20OneOf%3Cvals1%3E%20%2F%2F%20Is%20there%20a%20way%20of%20doing%20this%3F%0D%0A%0D%0Alet%20v1%3A%20Foo1%20%3D%20'A'%20%2F%2F%20This%20should%20work%0D%0Alet%20v2%3A%20Foo1%20%3D%20'D'%20%2F%2F%20This%20should%20give%20me%20an%20error%20since%20'D'%20doesn't%20exist%20in%20values%0D%0A%0D%0Alet%20vals2%20%3D%20%7B%0D%0A%20%20A%3A%20'A'%2C%0D%0A%20%20B%3A%20'B'%0D%0A%7D%0D%0Atype%20Foo2%20%3D%20keyof%20typeof%20vals2%0D%0Alet%20v3%3A%20Foo2%20%3D%20'A'%0D%0Alet%20v4%3A%20Foo2%20%3D%20'D'%20%2F%2F%20Type%20'%22D%22'%20is%20not%20assignable%20to%20type%20'%22A%22%20%7C%20%22B%22'

Question

Most helpful comment

in [email protected] and above. solve it like this

export const type = <const>[
  'room',
  'room_with_gifter',
  'user_send'
];

export interface Activity {
  id?: string;
  type: typeof type[number];
}

All 16 comments

keyof only works because it uses information known statically at compile time. Types in TS are fully erasable and don't exist in the transpiled code, so it's simply not possible to create a type based on the runtime contents of an array.

This is possible:

function stringLiterals<T extends string>(...args: T[]): T[] { return args; }
type ElementType<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer ElementType> ? ElementType : never;

const values = stringLiterals('A', 'B');
type Foo = ElementType<typeof values>;

const v1: Foo = 'A' // This should work
const v2: Foo = 'D' // This should give me an error since 'D' doesn't exist in values

Definitely not clear how to do this though. #27179 is related.

That’s still using information known statically by the compiler though; at that point I don’t understand what the advantage is over just saying ”A” | “B”.

@fatcerberus It's useful to avoid repeating information -- if you just write const values = ["A", "B"]; type Foo = "A" | "B"; it's easy for someone to change one of them while forgetting to change the other. You could write const values: Foo[] =["A", "B"];, but that's still susceptible to adding an additional entry to Foo and forgetting to put it in values.

Where are we on this, now ?

Like @andy-ms said it would be really useful to avoid repeating information manually.
And like @fatcerberus said, types are fully erasable. We sometimes need to runtime check data we get for example from a non pure part of application (like webservice, or from the user).

Building on @andy-ms's answer, and using const assertions introduced in typescript 3.4, it's now possible to do something like this

const values = ['A', 'B'] as const
type ElementType < T extends ReadonlyArray < unknown > > = T extends ReadonlyArray<
  infer ElementType
>
  ? ElementType
  : never

type Foo = ElementType<typeof values> // this is correctly inferred as literal "A" | "B"

This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.

in [email protected] and above. solve it like this

export const type = <const>[
  'room',
  'room_with_gifter',
  'user_send'
];

export interface Activity {
  id?: string;
  type: typeof type[number];
}

Instead of this:

export const items = <const>[
  'room',
  'room_with_gifter',
  'user_send'
];

export interface Activity {
  id?: string;
  type: typeof items[number];
}

You can do this:

export const items = [
  'room',
  'room_with_gifter',
  'user_send'
] as const;

export type Item = typeof items;

export interface Activity {
  id?: string;
  type: Item;
}

image

_Really don't like using type as a variable name._

@7kms

in [email protected] and above. solve it like this

export const type = <const>[
  'room',
  'room_with_gifter',
  'user_send'
];

export interface Activity {
  id?: string;
  type: typeof type[number];
}

Now what if I have this in a class?

class Class {
  const list = <const>[
   'room',
   'room_with_gifter',
   'user_send'
 ];

 const withType: typeof list[number];
}

Does not work. I also cannot do this.list[number]. Also, for my particular
usecase, I cannot set list to be static

@7kms

in [email protected] and above. solve it like this

export const type = <const>[
  'room',
  'room_with_gifter',
  'user_send'
];

export interface Activity {
  id?: string;
  type: typeof type[number];
}

Now what if I have this in a class?

class Class {
  const list = <const>[
   'room',
   'room_with_gifter',
   'user_send'
 ];

 const withType: typeof list[number];
}

Does not work. I also cannot do this.list[number]. Also, for my particular
usecase, I cannot set list to be static

@vegerot You can't use const directly in the scope of the class, so the syntax is incorrect.

@7kms

in [email protected] and above. solve it like this

export const type = <const>[
  'room',
  'room_with_gifter',
  'user_send'
];

export interface Activity {
  id?: string;
  type: typeof type[number];
}

Now what if I have this in a class?

class Class {
  const list = <const>[
   'room',
   'room_with_gifter',
   'user_send'
 ];

 const withType: typeof list[number];
}

Does not work. I also cannot do this.list[number]. Also, for my particular
usecase, I cannot set list to be static

@vegerot You can't use const directly in the scope of the class, so the syntax is incorrect.

@7kms My bad. I copied and pasted too much. What I meant to say is

class Class {
  private list = <const>[
   'room',
   'room_with_gifter',
   'user_send'
 ];

 private withType: typeof list[number];

// OR
  private withType2: typeof this.list[number];

}

etc. all do not work.
Also, for my particular usecase, I cannot set list to be static

@7kms

in [email protected] and above. solve it like this

export const type = <const>[
  'room',
  'room_with_gifter',
  'user_send'
];

export interface Activity {
  id?: string;
  type: typeof type[number];
}

Now what if I have this in a class?

class Class {
  const list = <const>[
   'room',
   'room_with_gifter',
   'user_send'
 ];

 const withType: typeof list[number];
}

Does not work. I also cannot do this.list[number]. Also, for my particular
usecase, I cannot set list to be static

@vegerot You can't use const directly in the scope of the class, so the syntax is incorrect.

@7kms My bad. I copied and pasted too much. What I meant to say is

class Class {
  private list = <const>[
   'room',
   'room_with_gifter',
   'user_send'
 ];

 private withType: typeof list[number];

// OR
  private withType2: typeof this.list[number];

}

etc. all do not work.
Also, for my particular usecase, I cannot set list to be static

class Class {
  private list = [
    "room",
    "room_with_gifter",
    "user_send"
  ] as const

  private withType: Class["list"][number]
}

@vegerot How about this?

@eyalch that's awesome! Thank you. I thought the solution would involve typeof or something. Would you give a quick explanation for how this works, or link me to the handbook where this is discussed?

@eyalch that's awesome! Thank you. I thought the solution would involve typeof or something. Would you give a quick explanation for how this works, or link me to the handbook where this is discussed?

@vegerot Sure, I believe it's an "Index type". Here it is in the Handbook: https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types

Glad I could help!

Hey %username%, if ElementType solution doesn't work for you too,
take attention on as const after array. It's crucial

Was this page helpful?
0 / 5 - 0 ratings

Related issues

blendsdk picture blendsdk  ·  3Comments

jbondc picture jbondc  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

kyasbal-1994 picture kyasbal-1994  ·  3Comments