I am attempting to save an item in an object using the object key as the discriminator for the type.
Refer to the edit below.
Below is a simple example:
type Foo = {
id: 'foo'
}
type Bar = {
id: 'bar'
}
type Container = {
foo: Foo
bar: Bar
};
const container: Container = {
foo: { id: 'foo' },
bar: { id: 'bar' },
};
function storeValue(value: Foo | Bar) {
container[value.id] = value; // <= Error occurs here. Refer to below.
}
The error message I receive is as follows:
TS2322: Type 'Foo | Bar' is not assignable to type 'Foo & Bar'.
Type 'Foo' is not assignable to type 'Foo & Bar'.
Type 'Foo' is not assignable to type 'Bar'.
Types of property 'id' are incompatible.
Type '"foo"' is not assignable to type '"bar"'.
I have tried the following approach:
type Container = {
[key in (Foo|Bar)['id']]: FooBar | undefined
}
Implementing this resolved the error... However, it also allows scenarios like assigning a Bar in container.foo:
function storeValue(value: Foo | Bar) {
container.foo = value; // This assignment should not be allowed.
}
Is there a way to deduce the type from the key
?
type Container = {
[key in (Foo|Bar)['id']]: ??? | undefined
}
I have consulted the documentation, FAQ, attempted various solutions, reviewed multiple StackOverflow posts and GitHub issues... Unfortunately, I have yet to find a suitable solution.
Edit: Another example (still simplified but more aligned with my use case. Note that I am utilizing Twilio Video)
type DataPublication = {
kind: 'data';
// other props
}
type AudioPublication = {
kind: 'audio';
// other props
}
type VideoPublication = {
kind: 'video';
// other props
}
type Publication = DataPublication | AudioPublication | VideoPublication;
class Whatever {
publications: {
data: DataPublication | undefined
audio: AudioPublication | undefined
video: VideoPublication | undefined
} = {
data: undefined,
audio: undefined,
video: undefined
}
handlePublishedWorking(publication: Publication) {
switch (publication.kind) {
case 'data':
this.publications.data = publication; // publication is narrowed to DataPublication
break;
case 'audio':
this.publications.audio = publication; // publication is narrowed to AudioPublication
break;
case 'video':
this.publications.video = publication; // publication is narrowed to VideoPublication
break;
}
}
handlePublishedNotWorking(publication: Publication) {
this.publications[publication.kind] = publication;
}
}