8

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 Answer 1

8

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".
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks, that almost worked for me, now the problem is that any 'optional' property on the Category becomes readonly and mandatory. I didn't include any optional in the example, but let's say I have a disabled?: boolean; there, it will error: Property 'disabled' does not exist on type '{ readonly val: "art"; readonly... etc.
@TrySpace - That's odd, I don't get that and I'm not sure where that error would come up, we're not doing anything to Category above. Can you update that playground example to demonstrate the problem?
@TrySpace - You're trying to use disabled on the array. It's not on the array, it's on array elements.
You're right, This is what I meant: typescriptlang.org/play?#code/… I cannot conditionally use the disabled property, unless it is certainly defined in the read-only object.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.