Reputation: 2359
I'd like to make a property optional based on the generic type. I've tried the following:
interface Option<T extends 'text' | 'audio' | 'video'> {
id: string;
type: T;
text: T extends 'text' ? string : undefined;
media: T extends 'audio' | 'video' ? T : undefined;
}
const option: Option<'text'> = { text: "test", type: "text", id: "opt1" };
So the idea is that the property text
only is defined for Option<'text'>
and media
only is defined for Option<'audio' | 'video'>
.
However, the ts compiler gives me the following error:
Property 'media' is missing in type '{ text: string; type: "text"; id: string; }'
but required in type 'Option<"text">'.ts(2741)
How can I work around this?
Upvotes: 5
Views: 2192
Reputation: 249606
You can't have the optionality of a property depend on a generic type parameter in an interface. You can however use a type alias and intersections instead:
type Option<T extends 'text' | 'audio' | 'video'> = {
id: string;
type: T;
}
& (T extends 'text' ? { text: string } : {})
& (T extends 'audio' | 'video' ? { media: T }: {});
const option: Option<'text'> = { text: "test", type: "text", id: "opt1" };
Although you are probably better off with a discriminated union:
type Option =
| { id: string; type: 'text'; text: string }
| { id: string; type: 'audio' | 'video'; media: 'audio' | 'video' };
const option: Extract<Option, {type: 'text' }> = { text: "test", type: "text", id: "opt1" };
function withOption(o: Option) {
switch(o.type) {
case 'text': console.log(o.text); break;
default: console.log(o.media); break;
}
}
Upvotes: 10
Reputation: 632
It's not possible to have a generic type using string values like what you are mentioned, interface Option<T extends 'text' | 'audio' | 'video'>
.
But if you want to have something like use below:
option.ts
interface Option {
id: string;
type: 'text' | 'audio' | 'video' | undefined;
}
media-option.ts
interface MediaOption extends Option {
media: string;
}
text-option.ts
interface TextOption extends Option {
text: string;
}
so; When you want to use them, you have to cast to a specific type between if it's media or text option based in type.
let option: Option = {id:'1',type: 'audio'}
if(option.type === 'audio'){
let media = option as MediaOption
}
Upvotes: 0
Reputation: 1074276
You can do it with a union:
type Option<T extends 'text' | 'audio' | 'video'> =
{
id: string;
type: T;
}
&
(
T extends 'text'
? {text: string}
: {media: T}
);
const option: Option<'text'> = { text: "test", type: "text", id: "opt1" };
Upvotes: 5