I have a very basic discriminated union, but for the discriminator, I only want to allow a specific values that are coming from another string literal union type. This would make it easier to add new union "cases" to the discriminated union.
Here's a string literal union which describes the allowed "values" for the type:
type AllowedType = "typeForNumber" | "typeForBoolean"
Then, my type to describe the data uses that string literal union:
type Data = {
type: AllowedType,
value: number | boolean
}
Based on my example, there are two options, which can have a more specific type for value
based on their type
field. I don't use these options, they're just here for demonstration purposes:
// Option 1 - "value" is a "number"
type DataOption1 = {
type: "typeForNumber",
value: number
}
// Option 2 - "value" is a "boolean"
type DataOption2 = {
type: "typeForBoolean",
value: boolean
}
So what I actually want to do is a discriminated union for Data
, because I can give more specific types for its value
field that way:
type Data =
| {
type: "typeForNumber"
value: number
}
| {
type: "typeForBoolean"
value: boolean
}
When you use the type, everything works fine:
const myData: Data = {
type: "typeForNumber",
// this creates an error, should be a `number`
value: "some string"
}
My question is: How do I make sure that the type
field in my Data
type can only be one of the options of the AllowedType
?
In the future, there will be more options for AllowedType
, so I want to limit the possible union types with them.
One could change add another union to the Data
type like so, without any error:
type Data =
| {
type: "typeForNumber"
value: number
}
| {
type: "typeForBoolean"
value: boolean
}
| {
type: "someOtherType"
value: string
}
This new union (with type: "someOtherType"
) shouldn't be allowed.
Is it possible to limit the discriminator (type
) in this discriminated union (Data
) from other string literal union type (AllowedType
)?
I tried to use a wrapper for the intersection, but the unions ignore (overwrite?) the type
type:
type AllowedType = "typeForNumber" | "typeForBoolean"
type DataWrapper = {
type: AllowedType
value: number | boolean
}
type Data = DataWrapper &
(
| {
type: "typeForNumber"
value: number
}
| {
type: "typeForBoolean"
value: boolean
}
)
CodePudding user response:
If I'm understanding correctly, the issue is Data
and AllowedType
possibly getting out of sync. If that's the issue, you could turn things around and define AllowedType
in terms of Data
, like this:
type Data =
| {
type: "typeForNumber"
value: number
}
| {
type: "typeForBoolean"
value: boolean
};
type AllowedType = Data["type"];
Then, adding to Data
automatically adds to AllowedType
.
In a comment you've said you're trying to protect against typos in the type
of the Data
union:
In my case, the Data type represents dynamic data coming from another source, so I want to be sure that there are e.g. no typos in the type values (like typeForNumber). That's why I'm so keen to find a solution to limit the type to specific values. Turning it around doesn't help in my case.
This seems to help with that:
type CheckData<DataType extends {type: AllowedType}> =
Exclude<DataType["type"], AllowedType> extends never
? DataType
: never;
Then this works:
type Data = CheckData<
| {
type: "typeForNumber"
value: number
}
| {
type: "typeForBoolean"
value: boolean
}>;
but this doesn't:
type Data2 = CheckData<
| {
type: "typeForNumber"
value: number
}
| {
type: "typeForBoolean"
value: boolean
}
| {
type: "typeForSomethingElse"
value: boolean
}>;
It doesn't catch all typos, though. It allows two types that both use the same type
value.