I have used Polymorphic relationships. So, right now I have the following TypeScript interface:
interface SubjectA {}
interface SubjectB {}
interface SubjectC {}
enum SubjectType {
SubjectA = 'Subject A',
SubjectB = 'Subject B',
SubjectC = 'Subject C',
}
interface ExampleSubject {
type: SubjectType;
subject: SubjectA | SubjectB | SubjectC
}
In this example, you can see, ExampleSubject.subject
has three possible subject types (SubjectA
, SubjectB
SubjectC
).
Now here I want it should resolve its type dynamically. For example, if ExampleSubject.type
is SubjectType.SubjectA
in that case ExampleSubject.subject
should be SubjectA
.
Please guide me, How can I resolve this? Thanks
CodePudding user response:
This can be achieved using a discriminated union type (docs)
interface SubjectA { someVar: 'a' }
interface SubjectB { someVar: 'b' }
interface SubjectC { someVar: 'c' }
enum SubjectType {
SubjectA = 'Subject A',
SubjectB = 'Subject B',
SubjectC = 'Subject C',
}
interface ExampleSubjectA {
type: SubjectType.SubjectA;
subject: SubjectA
}
interface ExampleSubjectB {
type: SubjectType.SubjectB;
subject: SubjectB
}
interface ExampleSubjectC {
type: SubjectType.SubjectC;
subject: SubjectC
}
type ExampleSubject = ExampleSubjectA | ExampleSubjectB | ExampleSubjectC
const subjectA: SubjectA = {
someVar: 'a'
}
const subjectB: SubjectB = {
someVar: 'b'
}
const exampleSubjectValid: ExampleSubject = {
type: SubjectType.SubjectA,
subject: subjectA
}
const exampleSubjectInvalid: ExampleSubject = {
type: SubjectType.SubjectA,
subject: subjectB
}
/* Type '{ type: SubjectType.SubjectA; subject: SubjectB; }' is not assignable to type 'ExampleSubject'.
The types of 'subject.someVar' are incompatible between these types.
Type '"b"' is not assignable to type '"a"'.(2322) */
Note: The SubjectX
interface definitions need to differ in some way otherwise TypeScript will collapse them into the same concept and they will effectively be three aliases for the same interface (and therefore can not be discriminated). This is why someVar
was included in the example / TS playground link
CodePudding user response:
For that, you'd use a discriminated union rather than an interface. You already have the discriminant (SubjectType
). Here's how you'd write the union:
type ExampleSubject =
| { type: SubjectType.SubjectA; subject: SubjectA }
| { type: SubjectType.SubjectB; subject: SubjectB }
| { type: SubjectType.SubjectC; subject: SubjectC };
Then if you had x
which was of type ExampleSubject
, you could narrow the type of x.subject
by checking x.type
:
if (x.type === SubjectType.SubjectA) {
console.log(x.subject);
// ^? (property) subject: SubjectA
}
Note that I've put something inside SubjectA
, SubjectB
, and SubjectC
so TypeScript can tell them apart. Since TypeScript's type system is structural, not nominal, when doing sample types like the above it's important to put something in the types to differentiate them from each other. Otherwise, they're compatible with each other, which can take you down the wrong path...