Your examples are experiencing issues with type narrowing due to the non-mutually exclusive nature of the type unions involved. This means that a variable of this type can have properties from both union members.
In the first scenario, even if you check for "num" in xyz, it can still be of type { str: string }
. This is because xyz may also have the property str: string from the second union member.
Similarly, in the second example, checking xyz.type === "num"
only partially narrows down the type without specifying it precisely. It is still possible for xyz to contain the property str: string
.
To ensure successful type narrowing, you should utilize a custom type guard function. For instance:
function isNum(obj: { num?: unknown }): obj is { num: number } {
return typeof obj.num === "number";
}
This type guard can help narrow down the type during runtime:
const xyz: { num: number } | { str: string } = { num: 42 };
if (isNum(xyz)) {
xyz // { num: number }
}
A similar approach can be taken for the second scenario with a different type guard function:
function isNum(obj: { type?: unknown }): obj is { type: "num", num: number } {
return obj.type === "num";
}
Implementing this type guard will aid in narrowing down the type effectively:
const xyz: { type: "num", num: number } | { type: "str", str: string } = { type: "num", num: 42 };
if (isNum(xyz)) {
xyz // { type: "num", num: number }
}