Home > Software design >  How can I create a typescript interface for a list of variables with similar names?
How can I create a typescript interface for a list of variables with similar names?

Time:10-06

I want to declare an interface that has many variables with similar name and I don't want to create it manually. The type is like

interface {
  "Var 1": string;
  "Var 2": string;
  "Var 3": string;
  "Var 4": string;
  "Var 5": string;
}

This list can go upto 55 so it isn't feasible to write all the variable names in the interface.

CodePudding user response:

You could use template literal types to turn a union of numeric types into keys of the sort you are looking for. Let's say you have a type named VarRange of the following form:

type VarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 
  | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20
  | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 
  | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40
  | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 
  | 51 | 52 | 53 | 54 | 55

Then you can write your interface as

interface VarInterface extends Record<`Var ${VarRange}`, string> { }

And verify that it works as expected

declare const vi: VarInterface;
vi["Var 51"] = "okay"
vi["Var 99"]; // error

Unfortunately there is no built-in method to easily create VarRange; it would be nice if you could write type VarRange = 1 .. 55, but TypeScript does not support such things as of TypeScript 4.4. There is an open feature request at microsoft/TypeScript#15480 to implement range types like this, but for now it does not exist.

So either you could use the hardcoded list like I have above, or you could write some more complicated type manipulation to make the compiler try to figure it out for you. In TypeScript 4.5 they will lift some recursion restrictions in the language and you'll be able to do this:

type TupleOf<N extends number, T = any, R extends T[] = []> =
  R['length'] extends N ? R : TupleOf<N, T, [T, ...R]>

type VarRange = Exclude<keyof TupleOf<56>, "0" | keyof any[]>
/* type VarRange = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" |
 "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "20" | "21" |
 "22" | "23" | "24" | "25" | "26" | "27" | "28" | "29" | ... 25 more ... | "55" */

interface VarInterface extends Record<`Var ${VarRange}`, string> { }

which uses recursive conditional types and variadic tuple types to build up a tuple of length 56, and grab all the numeric-like keys of it (excluding "0").


For TS4.1 through TS4.4 you could still do it but it's even more complicated, since the recursion limits are too shallow to reach 55. Maybe something like:

type Length7 = [0, 0, 0, 0, 0, 0, 0];
type Length14 = [...Length7, ...Length7];
type Length28 = [...Length14, ...Length14];
type Length56 = [...Length28, ...Length28];

type VarRange = Exclude<keyof Length56, "0" | keyof any[]>
/* type VarRange = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" |
 "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "20" | "21" |
 "22" | "23" | "24" | "25" | "26" | "27" | "28" | "29" | ... 25 more ... | "55" */

interface VarInterface extends Record<`Var ${VarRange}`, string> { }

But I don't know if either of these are worth it to you. At some point, maintaining a hardcoded list of numbers or strings is easier than maintaining complex type manipulation code.

Playground link to code

  • Related