Let's say I have the following types (syntax is Elm-ish/Haskell-ish):
type Reply = LoginReply | LogoutReply
type LoginReply = LoginSucceeded | AlreadyLoggedIn String
If I try to model it using Typescript's discriminated unions, I'll face the problem that LoginReply
needs to have a property kind
with value "loginReply" but it can't because it's declared using type
keyword, not class
.
That's my best shot at the problem till now:
type Reply = LoginReply | LogoutReply
type LoginReply = LoginSucceeded | AlreadyLoggedIn
interface LoginSucceeded {
kind: "loginSucceeded";
}
interface AlreadyLoggedIn {
kind: "alreadyLoggedIn";
loggedInUsername: string;
}
interface LogoutReply {
kind: "logoutReply";
}
As you can see, one can't even use "loginReply"
anywhere and thus can't be used in discrimination.
The only workaround I have for knowing a Reply
variable is a LoginReply
is to see if its kind
is one of "loginSucceeded"
and "alreadyLoggedIn"
.
So, how do I achieve what I want in Typescript? How to create a discriminated union type whose child types are discriminated-union types themselves?
Since LoginReply
is just a union of LoginSucceeded | AlreadyLoggedIn
LoginSucceeded | AlreadyLoggedIn
, from the type system perspective there is no difference between type Reply = LoginReply | LogoutReply
type Reply = LoginReply | LogoutReply
and type Reply = LoginSucceeded | AlreadyLoggedIn | LogoutReply
type Reply = LoginSucceeded | AlreadyLoggedIn | LogoutReply
type Reply = LoginSucceeded | AlreadyLoggedIn | LogoutReply
. The type
keyword introduces a type alias which as the name suggests is just a convenient name for the type not a new type in itself.
You have two options, neither of them 100% what you want.
You can check all possible values kind
of LoginReply
as you mentioned you considered:
function doStuff(o: Reply) {
switch(o.kind)
{
case 'alreadyLoggedIn' :
case 'loginSucceeded' :
o; /* is LoginReply */ break;
case 'logoutReply': o // o is LogoutReply
}
}
Or you can add an extra subKind
field for the LoginReply
subtypes:
export type Reply = LoginReply | LogoutReply
type LoginReply = LoginSucceeded | AlreadyLoggedIn
interface LoginSucceeded {
subKind: "loginSucceeded";
kind: "loginReply";
}
interface AlreadyLoggedIn {
kind: "loginReply";
subKind: "alreadyLoggedIn";
loggedInUsername: string;
}
interface LogoutReply {
kind: "logoutReply";
logout: boolean
}
function doStuff(o: Reply) {
switch(o.kind)
{
case 'loginReply' : o // is LoginReply
switch(o.subKind) {
case 'alreadyLoggedIn' : o; /* is AlreadyLoggedIn */ break;
case 'loginSucceeded' : o; /* is LoginSucceeded */ break;
}
break;
case 'logoutReply': o // LogoutReply
}
}
Will keep this answer for all solutions. Please, update if you have another solution.
Solution#1 : As described in the question, see if kind
is one of "loginSucceeded"
and "alreadyLoggedIn"
.
Solution#2 : As described by Titian Cernicova-Dragomir in his answer , use kind
and subKind
.
Solution#3 :
If one argues that Solution#2 makes LoginSucceeded
and AlreadyLoggedIn
know too much about them being discriminated types of discriminated types, one could also argue that, even under one level of discrimination, they shouldn't have had knowledge of kind
. If one follows that argument, one could implement a solution like this:
type Reply = { kind: "loginReply", t: LoginReply } | { kind: "logoutReply", t: LogoutReply }
type LoginReply = { kind: "loginSucceeded", t: LoginSucceeded } | { kind: "alreadyLoggedIn", t: AlreadyLoggedIn }
interface LoginSucceeded {
}
interface AlreadyLoggedIn {
loggedInUsername: string;
}
interface LogoutReply {
logout: boolean
}
function doStuff(o: Reply) {
switch(o.kind)
{
case 'loginReply' : o.t; // is LoginReply
const loginReply = o.t;
switch(loginReply.kind) {
case 'alreadyLoggedIn' : loginReply.t; /* is AlreadyLoggedIn */ break;
case 'loginSucceeded' : loginReply.t; /* is LoginSucceeded */ break;
}
break;
case 'logoutReply': o.t; // LogoutReply
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.