typescriptenumstype-inferencesingleton-type

In TypeScript, can I specify the type of object fields while still getting the literal key types inferred?


What I'm trying to do is to define some kind of "rich enumeration", where each enum key is linked to some data whose type I want to specify.

E.g., something like this:

const Seasons = {
    winter: { temperature: 5, startMonth: "December" },
    spring: { temperature: 20, startMonth: "March" },
    summer: { temperature: 30, startMonth: "June" },
    fall: { temperature: 15, startMonth: "September" },
} as const

This declaration is nice and lets me do things like:

type Season = keyof typeof Seasons // "winter" | "spring" | "summer" | "fall"

and even a type guard like

function isSeason(s: string): s is Season {
    return Object.keys(Seasons).includes(s)
}

What I cannot do, though, is get the compiler to check that all “season definitions” have a given type. If I define this:

type SeasonData = typeof Seasons[Season]

then SeasonData is the union of the type of all definitions—no matter if they have the same shape or not.

So I'm looking for a syntactically non-redundant and light way to define something like:

const Seasons: EnumWith<{temperature: number, startMonth: string}> = ... // as before
               ^^^^^^^^ <- to be defined!

Especially, I'm trying not to have to repeat the list of seasons in any other structure (interface or array) and to directly infer the type Season from the object definition (though hearing about alternative is always good!).

What could be done?


Solution

  • I'm not sure I fully understand your use case, the way I'd model the relationship between your types is something like the following

    type Season =
      | "winter"
      | "spring"
      | "summer"
      | "fall"
    
    type Month =
      | "January"
      | "February"
      | "March"
      | "April"
      | "May"
      | "June"
      | "July"
      | "August"
      | "September"
      | "October"
      | "November"
      | "December"
    
    type SeasonStruct = {
      temperature: number
      startMonth: Month
    }
    
    type Seasons = { [K in Season]: SeasonStruct }
    
    const seasons: Seasons = {
        winter: { temperature: 5, startMonth: "December" },
        spring: { temperature: 20, startMonth: "March" },
        summer: { temperature: 30, startMonth: "June" },
        fall:   { temperature: 15, startMonth: "September" },
    }
    

    This should give you enough building blocks to represent everything you need in your domain, hope it helps.