Get string literal types from Array of objects

Viewed 448

So from:

export interface Category{
  val: string;
  icon: string
}
const categoryArray: Category[] = [
  {
    val: 'business',
    icon: 'store'
  },
  {
    val: 'media',
    icon: 'video'
  },
  {
    val: 'people',
    icon: 'account'
  },

  ... 

I'd like to get a Union type back like this:

'business' | 'media' | 'people' ... 

I don't know what kind of syntax or helpers there are for this, maybe none at all. I realise this way might be backwards, and should perhaps use an Enum, but before that, I want to know it it's possible.

Some fictional examples of what I'd like to do, but the solution I expect to be more complex

type Cats = keysof[] categoryArray 'val'  
type Cats = valuesof categoryArray 'val'

The following is close, but returns string:

export type CatsValType = typeof categories[number]['val']

Or the following; instead of the types I need the string literals

type ValueOf<T> = T[keyof T];
type KeyTypes = ValueOf<typeof categories[number]> // Returns: `string`

There are similar questions like: Is there a `valueof` similar to `keyof` in TypeScript? but they don't assume an array of objects.

And the example here: https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html is similar, but I don't want to return the type, but the value of the fields, so I get a Union type back.

1 Answers

You can do it if:

  • The values in the array don't change at runtime (since type information is a compile-time-only thing with TypeScript); and

  • You tell TypeScript that they values won't change by using as const; and

  1. You don't give the categoryArray constant the type Category[], because if you do the result would just be string (because Category["val"]'s type is string) rather than the string literal union type you want.

Here's an example (playground link):

export interface Category{
  val: string;
  icon: string
}
const categoryArray = [
  {
    val: 'business',
    icon: 'store'
  },
  {
    val: 'media',
    icon: 'video'
  },
  {
    val: 'people',
    icon: 'account'
  },
] as const;

type TheValueUnion = (typeof categoryArray)[number]["val"];
//   ^? −− "business" | "media" | "people"

The key bits there are the as const and type TheValueUnion = (typeof categoryArray)[number]["val"];, which breaks down like this:

  1. typeof categoryArray gets the type of categoryArray (the inferred type, since we didn't assign a specific one).
  2. [number] to access the union of types indexed by number on the type of categoryArray.
  3. ["val"] to access the union of types for the val property on the union from #2, which is the string literal type you want: "business" | "media" | "people".
Related