1

for the following example, I'm trying to discriminate the base type during a type declaration

type Schedule = {
  flag_active : boolean,
}
type Channel = {
  flag_archived : boolean
}

type CreateChangeLog = {
  from : null,
  to : Schedule | Channel
}
type DeleteChangeLog = {
  from : Schedule | Channel,
  to : null
}

type AllChanges = CreateChangeLog | DeleteChangeLog

given the type AllChanges whose definition I can't alter, how could I retrieve the Schedule type?

A. L
  • 11,695
  • 23
  • 85
  • 163
  • you can definitely narrow it down by using `if (change.from && "flag_active" in change.from)` in a for loop – A. L Feb 10 '23 at 03:33
  • I'm not following the question, exactly. Do you want [this approach](https://tsplay.dev/NnlXVW) maybe? If not, what am I missing? – jcalz Feb 10 '23 at 03:48
  • @jcalz It can't be a const, as I do not know the exact structure of the array at runtime (data retrieved from API). I want to be able to retrieve `Schedule` type from `AllChanges` type, without needing to run through actual code. It should be similar to https://stackoverflow.com/questions/46312206/narrowing-a-return-type-from-a-generic-discriminated-union-in-typescript but I can't seem to get it to work with a "has this key" – A. L Feb 10 '23 at 04:05
  • 1
    I guess I'm really not understanding your question. What is the point of `allChanges` in the example? Could [this approach](https://tsplay.dev/W4jyvN) be what you're looking for? If so I could maybe write up an answer (although you should probably [edit] the question to remove all references to `allChanges`). If not, uh, could you clarify? – jcalz Feb 10 '23 at 04:14
  • @jcalz yeah, `Extract` was what I was looking for – A. L Feb 10 '23 at 04:20
  • Okay so I'll write up an answer; could you [edit] the question to remove `allChanges`? – jcalz Feb 10 '23 at 04:31
  • @jcalz I think the `allChanges` is relevant as I'm trying to do `Extract`. The extraction is being done in another file which is importing the variable – A. L Feb 10 '23 at 04:34
  • Given that multiple people addressed the question by trying to change how you defined `allChanges`, I think it's more of a drawback than a benefit to it being in the question. If you phrased the question like "given the type `AllChanges` whose definition I can't alter, how could I retrieve the `Schedule` type", it might be more clear. But I won't belabor the point anymore. – jcalz Feb 10 '23 at 04:39
  • 1
    @jcalz I have changed it. Feel free to suggest an edit if it would be clearer – A. L Feb 10 '23 at 04:59

1 Answers1

1

If you have a union type like ScheduleOrChannel as computed here:

type AllChanges = CreateChangeLog | DeleteChangeLog

type ScheduleOrChannel = NonNullable<AllChanges["from"]>;
// type ScheduleOrChannel = Schedule | Channel

and you want to filter it to include only those union members that match a certain supertype, you can use the Extract<T, U> utility type as shown here:

type JustSchedule = Extract<ScheduleOrChannel, { flag_active: any }>
// type JustSchedule = Schedule

type JustChannel = Extract<ScheduleOrChannel, { flag_archived: any }>
// type JustChannel = Channel

Extract<T, U> is just a distributive conditional type which is implemented as

type Extract<T, U> = T extends U ? T : never

so you could always write your own custom union-filtering operation that uses other criteria for keeping/rejecting members, such as a HasKey utility type:

type HasKey<T, K extends PropertyKey> =
    T extends unknown ? K extends keyof T ? T : never : never;

type JustSchedule1 = HasKey<ScheduleOrChannel, "flag_active">
// type JustSchedule1 = Schedule

type JustChannel2 = HasKey<ScheduleOrChannel, "flag_archived">
// type JustChannel2 = Channel

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360