Joff
Joff

Reputation: 12197

typescript: make type that fits object literal values

given this object literal...

const views = {
  default: 'DEFAULT',
  user: {
    home: 'HOME',
    profile: 'PROFILE'
  }
}

I want to get a type like below without having to define it in multiple ways like defining a union type and then an object literal with all the types embedded

interface State {
  view: `DEFAULT' | 'HOME' | 'PROFILE'
}

Can I achieve this in Typescript?

EDIT:

I could defined a union type of strings

type View = 'HOME' | 'DEFAULT' | 'PROFILE'

and then declare the object literal (with the same values as the type above) but then I would have to define it in multiple ways and I would be repeating myself

Upvotes: 2

Views: 2806

Answers (1)

Nurbol Alpysbayev
Nurbol Alpysbayev

Reputation: 21971

If I got you correctly, this is what you want:

type View = 'HOME' | 'DEFAULT' | 'PROFILE'

interface Views {
  [key: string]: View | Views
}

const views: Views = {
  default: 'DEFAULT',
  user: {
    home: 'HOME',
    profile: 'PROFILE'
  },
  wrong: 'SOME_STRING', // error
}

UPD, after comments. Now, if you want to object literal be a reference of all possible strings you could naively do this:

const views = {
 default: 'DEFAULT',
    user: {
      home: 'HOME',
      profile: 'PROFILE'
    },
  }

// Make some peculiar types to extract all the strings
type Views = typeof views

type Strings<Obj> = Obj[keyof Obj]
type FlatStrings<T> = T extends object ? T[keyof T] : T

type View = FlatStrings<Strings<Views>>

But guess what type does View have? It's just string! Not DEFAULT | HOME | PROFILE as expected. Because typescript infers type string from object literal strings unless you rewrite the object literal like this:

const views = {
    default: 'DEFAULT' as const,
    user: {
      home: 'HOME' as const,
      profile: 'PROFILE' as const
    },
  }

Upvotes: 5

Related Questions