I have data coming in which every record matches this template:
type G = { kind: string; value: any; };
Where kind is going to be any string out of a finite set, but that set is huge enough, and I need to actually deal with few enough of them, that I don't want to list them all, plus more may be added in future upstream and I don't want my code to break.
I care about specific record kinds, for example these two:
type A = { kind: "a", value: number; }
type B = { kind: "b", value: string; };
Then I have my discriminated union:
type MyRecord = A | B | G;
The real types are more complex; this is just a general idea.
The problem is that when I try to narrow types, for example by having a conditional for x.kind === "a", the G is always still a possibility as far as TS is concerned, and so the type isn't properly narrowed to A. So for x.value I have the type any rather than the number I expect. This is because a record with kind equal to "a" satisfies G as well as A.
Is there a way to type G.kind as "any string except any of the literals elsewhere in this union", and so have the narrowing working as I expect?
I've tried defining G like this instead:
type G = kind: Exclude<string, A["kind"] | B["kind"]>; value: any; }
But that doesn't help -- it still narrows to A | G when I expect it to narrow to A.
I suppose I could just not define the G type at all -- would that be the best way to go? I don't like that I'm then not as type safe, since TS won't complain if I don't handle the case where none of the discriminators matched.
Or I can have a type guard for each of the types I've defined, and use if-elses. That's quite a lot of extra boilerplate though.