Search code examples
typescripttemplate-literalsmapped-types

TypeScript types based on capitalization of a key


Trying to add types to an existing JS library I'm using. The library unfortunately has a rule that the type of the value in an object is somewhat determined by the capitalization of the first letter of its associated key. I thought something like the below might work, but it does not.

type UppercaseLetters = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z';
type LowercaseLetters = Lowercase<UppercaseLetters>;

type RefKey = `${UppercaseLetters}${string}`
type PropKey = `${LowercaseLetters}${string}`

// Define these types just so it compiles
type SomeRefType = number;
type SomePropType = boolean;

type Spec = {
  type: string
} & {
  [s in RefKey]: SomeRefType
} & {
  [s in PropKey]: SomePropType
};

This compiles, but the actual type that Spec becomes is:

type Spec = {
    type: string;
} & {} & {}

Any ideas out there? Perhaps this case is just too out of the left field for TypeScript to handle.

Example object:

const specObj: Spec = {
  type: 'Some string',
  Apple: 3,
  Orange: 6,
  lowerCaseKey: false,
  anotherOne: true
}

Solution

  • As noted by Alateros in the comments, since typescript@4.4 you can use index signatures for template literals.

    Though you still have to ensure type field must be required and may have the type that is not compatible with lowercased keys type. So you may write Spec type like that:

    type Spec = {
      [K in RefKey | PropKey]: 'type' extends K 
          ? string 
          : K extends RefKey 
              ? SomeRefType 
              : K extends PropKey 
                  ? SomePropType
                  : never
    } & {
      type: string
    }
    

    playground link