#Can I use an enum as a discriminator in a discriminated union?

59 messages · Page 1 of 1 (latest)

heady siren
#

I'm just checking. I think this is not "allowed" but want to make sure

modest relic
heady siren
#

are you sure? or, well, I"m trying to do this

enum Disc {
A
B
}
type MyTypeA {
discriminator: "A"
...
}
type MyTypeB {
discriminator: "B"
...
}

and that doesn't seem to work but I can't do

discriminator: Disc.A
modest relic
#

But the value of Disc.A is not "A"
It is the number 0
And Disc.B is 1

#

If you want the values to be those strings, then set it explicitly

enum Disc {
  A = "A",
  B = "B",
}
heady siren
#

omg it does work. It's one of those time where you are making a bunch of changes trying to find the solution and you don't isolate each change sufficiently. Sorry. Thanks for clearing it up for me.

heady siren
#

@modest relic Hi not sure if you're till around. I'm having a trouble with these distinguishers again. Pretty sure I'm not doing it right.
I have my type definition in which I'm doing {myDist: "A"} and then I'm explicitly setting the value for myDist to "A" on the concrete object as well. but when I try to say if(X.myDist === "A") it just doesn't narrow

simple moat
#

@heady siren Can you make a reproduction?

spring root
hollow mantle
#

yeah, and on top of that ☝️ if you don't want to refer to the enum itself in type definitions or runtime code then there's no benefit to having it (just use a union of "A" | "B")

heady siren
#

Hi, thanks for the responses.
So a) you say "if you don't want to refer to the enum in the type def" I don't mind, I just don't know how, i.e. don't know the syntax. b) I kind of have to use the enum because we have the enum for other purposes and I don't want to refer to the same list of values in different ways.
For now I've had to resort to using in "'somekeyinthedistinctType' in generalType" to get narrowing

spring root
#

that kinda defeats the purpose of using a DU then

heady siren
#

I agree.

hollow mantle
strong mirageBOT
#
mkantor#0

Preview:```ts
enum Disc {
A,
B,
}

type MyTypeA = {
discriminator: Disc.A
a: string
}

type MyTypeB = {
discriminator: Disc.B
b: number
}

declare const aOrB: MyTypeA | MyTypeB
if (aOrB.discriminator === Disc.A) {
aOrB.a
// ^?
} else {
aOrB.b
// ^?
...```

spring root
heady siren
#

So, when I did 'discriminator: Disc.A' I got an error in the ide. Which makes me think, maybe it was an "error" like a lint error not an "I wont compile for you error" which is very frustrating because that's a whole different story. I"ll have to try again and see

#

Well, I mean I have to in the sense that in the code base there is a list of values. I can reprodue that list of values in another way e.g. an enum and a string union, but then I have to keep the two insync.

spring root
#

in most cases you won't need 2 separate lists that you would need to keep synced

#

you can have a single source of truth then derive the other from that

heady siren
#

true. just sayin, I got one may as well. use it. Thank you for the help. I'm going to try this out and I'll get back with the results in a little bit.

heady siren
#

@spring root Hi, so here is what's happening. When I define my types they inherit some base properties from a basetype. that base type has the distinguisher on it, however the distinguisher is optional because the base type is used else where too. So I define my type like this

export type FinancialIncident = Omit<Incident, 'vertical'> & {
  vertical: 'FINANCIAL';
...

then in the code I do if (incident.vertical === 'FINANCIAL') { and I try to use the incident as a FinancialIncident. but I get this error

The intersection 'Omit & { vertical: "FINANCIAL"; parties: FinancialParty[]; directorOrOfficerType?: DirectorOrOfficerType | undefined; financialIncidentId?: string | undefined; } & { ...; } & { ...; } was reduced to 'never because property vertical has conflicting types in some constituents.

spring root
#

right, that would be because you're trying to narrow Incident to FinancialIncident via vertical, which are unrelated in those 2 types

#

how does Incident define vertical?

heady siren
#

well, so Incident is a base type and it has vertical defined, basically like vertical?: 'FINANCIAL'|'LIABILITY'|...etc; But then as above FinancialIncident has vertical defined as 'FINANCIAL'

spring root
#

ok, so just don't use Omit

strong mirageBOT
#
type Wide = { a?: "a" | "b", b: number, c: string }
//   ^? - type Wide = {
//       a?: "a" | "b";
//       b: number;
//       c: string;
//   }
type Narrow = Wide & { a: "a", b: 1 | 2 }
//   ^? - type Narrow = Wide & {
//       a: "a";
//       b: 1 | 2;
//   }
spring root
#

this would also let you catch the issue quicker if you mistype the value, even more so in an interface

strong mirageBOT
#
interface Parent { a: "a" | "b" }
type WrongType = Parent & { a: "c" }
type W_ = WrongType["a"]
//   ^? - type W_ = never
interface WrongI extends Parent {
//        ^^^^^^
// Interface 'WrongI' incorrectly extends interface 'Parent'.
//   Types of property 'a' are incompatible.
//     Type '"c"' is not assignable to type '"a" | "b"'.
  a: "c"
}
heady siren
#

well, so I need to omit somethings. so what I just tried was removing 'vertical' from omit but the error persists

spring root
#

that's likely not the case

#

you don't need to Omit anything, you just need to set up the base types correctly, to say that any of the child types is also a valid base type

#

that's the point of base types

#

using Omit obscures that purpose, making issues harder to track

#

you have something like { vertical: "a" } & { vertical: "b" } somewhere
the solution is to fix the mistake in the definition, not to Omit more stuff

#

it may be a typo somewhere

heady siren
#

interesting. I might need some help with that. Basically I have a short graph
Case

  • Incident
    • Party[]
      • Contact[]

where all those are base types. Then I have
FinancialCase

  • FinancialIncident
    • FinancialParty[]
      • FinancialContact[]

where the F types have just a couple properties in them. So what I do is this

export type CyberIncident = Omit<Incident, 'parties'> & {
  vertical: 'CYBER';
  parties: CyberParty[];
};

In order to make sure the CyberIncident has a collection of CyberParty(s) rather than just party(s)

spring root
#

if CyberParty is a subtype of Party then you don't need that Omit at all

heady siren
#

really? that would certainly simplfy things. It's hard to know when I can't get it to narrow properly, but solving one may solve the other

spring root
#

well, how's CyberParty defined?

#

or perhaps more straightforward, is CyberParty just a more specific Party?

#

if so, that's what subtypes are

heady siren
#

Yes, so it's defined just the same way as CyberIncident above. Although I left out the extra properties so its more like this

export type CyberIncident = Omit<Incident, 'parties'> & {
  vertical: 'CYBER';
  parties: CyberParty[];
  cyperProperty1: stirng
  cyberProperty2: 
};
spring root
#

so yeah if you're just specifying subtypes then you don't need Omits at all

heady siren
#

That is awesome. as per usual I am over complicating things. I"ll give it a try.
thanks!

heady siren
#

So should I still "add" the subtypes? e.g. if Incident has parties: Party[] In cyberIncident I don't omit "parties" but should I add a property parties: CyberParty[] ?

spring root
#

yeah

#

because the new one will be intersected with the base type

heady siren
#

@spring root Hello, I wonder if we could spend a minute on this thing again. I'm afraid it's due eod on monday so I may not get there, but it's driving me crazy

#

What I have is a graph of base types. Now those basetypes are ... not very simple but ultimately I know what is on them. Then I have my subtypes. These subtypes no longer omit anything and they are simple maybe just a couple new properties and the "vertical" value but when I go to try to use it I get
incident.vertical === 'CYBER' && incident.cyberCrime "cyberCrime doesn't exist on incident"

#

one thing is that the base type does have vertical: CYBER"| "FINANCIAL"| etc on it and I'm not overriding that or omiting it at all. except in the subtypes where I am overriding it

hollow mantle
#

@heady siren can you share a simplified example of what you have now?

#

the type of incident should look like type Incident = SpecificIncident1 | SpecificIncident2 | … if you want to be able to discriminate it

heady siren
#

Hey, sorry I didn't work up an example. I really fried and still have work to do. But I see you comment here. The issue is that Incident is a base type that SpecificIncident1 etc extend. So would it be
type Incident = SpecificIncident1 | SpecificIncident2 | {whole bunch of stuff for the baes type} ?

hollow mantle
#

it's hard to be sure without knowing exactly what you're doing, but it sounds like you need two separate things:

  1. a set of common properties that you don't want to write over and over again (the "base type")
  2. the type of incident, which is specifically either a CyberIncident or a WhateverOtherKindOfIncident
    does that sound right?
#

if so, you'd define the common supertype stuff as an object type in BaseIncident, and type incident as a union like i mentioned above. CyberIncident would intersect with BaseIncident, not that union type

heady siren
#

Interesting.

#

I guess it's not a discriminated union, unless there is a freakin union. hmmm. I hope that doesn't cause too much chaos through out my code. But that does point to the culprit I think!
Thank you!