'TypeScript: Conditional Intersection via Discriminated Union
I'm try to efficiently add properties to an object type based on one of the existing property values. For example, there is a default type BaseField shared between many types of fields. The type of the field comes from an enum. For example:
enum FieldType {
Currency = 'currency',
Text = 'text',
}
type BaseField<T extends FieldType = FieldType> = {
id: string;
type: T;
}
Some fields have additional properties depending on their type. For example, a field with type Currency also has a defaultCurrency property. I originally tried to use a map and a discriminated union to achieve this, something like:
type CurrencyField = {
defaultCurrency: string;
}
type FieldMap = {
[FieldType.Currency]: CurrencyField,
[FieldType.Text]: never,
}
type Field<T extends FieldType = FieldType> = T extends T
? BaseField<T> & FieldMap[T]
: never;
This half works. If I create a field as such:
const field: Field = {
id: 'field1',
type: FieldType.Currency,
}
I correctly get a type error Property 'defaultCurrency' is missing in type. And, if I add it in, the type is valid. However, the issue arises when I use one of the types where the FieldMap has the value of never. If I try to make a text field:
const field: Field = {
id: 'field2',
type: FieldType.Text,
}
I see the error Type 'FieldType.Text' is not assignable to type 'FieldType.Currency'.. I have tried a few ways to get around this:
- Instead of
neveruseBaseFieldas the type in the map - Remove the params from
BaseFieldtype - Modify the union to
BaseField<T> & T extends T ? FieldMap[T] : never;
However, I am unable to resolve this. I can get it to work by manually creating the fields and joining them together:
type CurrencyField = {
id: string;
defaultCurrency: string;
type: FieldType.Currency;
}
type TextField = {
id: string;
type: FieldType.Text;
}
type Field = CurrencyField | TextField;
However, I'd like to keep this as scalable as possible and would rather not have to create new types every time a new field was added. Is there a way to achieve this? Thank you for your help!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
