This response is quite similar to a previous one by @LindaPaiste, but with a slight tweak in the approach where the mapping from names to types is stored in its own dedicated type. This separate type is then manipulated to generate Variable
. For instance, your mapping structure might resemble this:
type TypeMapping = {
number: number;
string: string;
boolean: boolean
// add more here as needed
}
Subsequently, the definition of Variable
could be formed as follows:
type Variable = { [K in keyof TypeMapping]: {
name: string;
required: boolean;
type: K;
defaultValue: TypeMapping[K];
} }[keyof TypeMapping]
The concept behind this design involves taking each key K
from TypeMapping
, and changing the property type from TypeMapping[K]
to the subtype of Variable
for that particular key K
(where type
corresponds to the key, and defaultValue
denotes the property type). The resulting mapped type doesn't align exactly with what is sought because it retains the same keys as TypeMapping
. By indexing into it, we obtain the union of its properties.
Outcome:
/* type Variable = {
name: string;
required: boolean;
type: "string";
defaultValue: string;
} | {
name: string;
required: boolean;
type: "number";
defaultValue: number;
} | {
name: string;
required: boolean;
type: "boolean";
defaultValue: boolean;
} */
With this in place, you can observe the desired functionality:
const newWorkingVar: Variable = {
name: "count",
required: true,
type: "number",
defaultValue: 22 // valid
}
const newErrorVar: Variable = {
name: "count",
required: true,
type: "number",
defaultValue: "test" // error!
}
Playground link for code