Get string literal types from Array of objects
Asked Answered
W

1

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.

Wes answered 13/1, 2022 at 13:56 Comment(0)
B
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".
Bulletproof answered 13/1, 2022 at 14:1 Comment(7)
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.Wes
@Wes - 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?Bulletproof
typescriptlang.org/play?#code/…Wes
@Wes - You're trying to use disabled on the array. It's not on the array, it's on array elements.Bulletproof
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.Wes
While technically the disabled prop doesn't exists, I need to conditionally check the presence of the disabled prop. As if I had categoryArray: Category[] = ... defined, but I can't use both that AND TheValueUnion...Wes
@Wes - You could always do something like this. :-) (You could even do a runtime validation check to make sure the type assertion isn't wrong as the code changes over time.)Bulletproof

© 2022 - 2024 — McMap. All rights reserved.