'I'm facing weird TypeScipt build-in limit. Can someone explain?
In this TypeScript example
type SomeType = "1" | "2" | "3" | "4" | "5" | "6" | "7";
type SomeStatus = "one" | "two" | "three" | "four" | "five" | "six" | "seven";
type TypeDiffer = {
type: "1"
} | {
type: Exclude<SomeType, "1">
}
type StatusDiffer = {
status: "one"
} | {
status: Exclude<SomeStatus, "one">
}
type OrderStatusFull = StatusDiffer & TypeDiffer;
const apiResponse: any = {};
const example: OrderStatusFull = {
type: apiResponse.type as SomeType,
status: apiResponse.status as SomeStatus
}
I've got the Typescript error:
Type '{ type: SomeType; status: SomeStatus; }' is not assignable to type 'OrderStatusFull'.
Type '{ type: SomeType; status: SomeStatus; }' is not assignable to type '{ status: "two" | "three" | "four" | "five" | "six" | "seven"; } & { type: "2" | "3" | "4" | "5" | "6" | "7"; } & { status: SomeStatus; }'.
Type '{ type: SomeType; status: SomeStatus; }' is not assignable to type '{ status: "two" | "three" | "four" | "five" | "six" | "seven"; }'.
Types of property 'status' are incompatible.
Type 'SomeStatus' is not assignable to type '"two" | "three" | "four" | "five" | "six" | "seven"'.
Type '"one"' is not assignable to type '"two" | "three" | "four" | "five" | "six" | "seven"'.
When I reduce number of types and statuses to 5 (or 3 and 7) it works. It works if multiplied number of items is up to 25.
type SomeType = "1" | "2" | "3" | "4" | "5"; // | "6" | "7";
type SomeStatus = "one" | "two" | "three" | "four" | "five"; // | "six" | "seven";
...
It also works with full list of types and statuses when I remove as SomeStatus, but when I define apiResponse more precisely, the problem is back.
const apiResponse: {
type: SomeType,
status: SomeStatus
} = {
type: "1",
status: "one"
}
const example: OrderStatusFull = {
type: apiResponse.type,
status: apiResponse.status
}
The only workaround is force type's or status's type to subset of the type, like this:
const example: OrderStatusFull = {
type: apiResponse.type as "1" | "2",
status: apiResponse.status
}
Can anyone explain why this happen and how to use type checking without workarounds?
The real world scenario is for example stock exchange api response, when based on order status and type combination different properties are filled.
Update: I've made stock exchange example in Playground. I omit the problem here, just to show which use cases I'm trying with union types in real life situations.
Solution 1:[1]
In this case it worth using discriminated unions:
type SomeType = "1" | "2" | "3" | "4" | "5" | "6" | "7";
type SomeStatus = "one" | "two" | "three" | "four" | "five" | "six" | "seven";
interface Base {
status: SomeStatus,
type: SomeType
}
interface OrderStatusWithOne extends Base {
status: 'one',
type: '1'
}
interface OrderStatusWithoutOne extends Base {
status: Exclude<SomeStatus, "one">,
type: Exclude<SomeType, "1">
}
type OrderStatusFull = OrderStatusWithOne | OrderStatusWithoutOne
declare let apiResponse: OrderStatusFull
const example: OrderStatusFull = {
type: apiResponse.type, // type is "1" | "2" | "3" | "4" | "5" | "6" | "7"
status: apiResponse.status // status is "one" | "two" | "three" | "four" | "five" | "six" | "seven"
}
However there is still an error because type property of apiResponse is in fact a union of all possible types. The problem is in destructuring. You are not allowed to use OrderStatusFull for destructured object of this type. Again, type is a union of 1 and Exclude<SomeType, "1"> - it means that it does not meet requirements in OrderStatusFull union.
In order to make it a bit safer, you can use function:
const handle = ({ type, status }: OrderStatusFull): OrderStatusFull =>
type === '1'
? { type, status }
: { type, status };
I know, it looks weird but it works. If you don't like extra function approach I think it will be justified if you use type assertion in this case.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
