I have a special case, where I need to write an object configuration for predefined filters. I would like to use typescript generics to accomplish this task.
So, I have the following configuration:
const filterOptions: FilterOption[] = [
// These should be valid
{ field: 'name', operator: 'sw', operand: 'Mr.' },
{ field: 'age', operator: 'lt', operand: 18 },
{ field: 'joinDate', operator: 'gte', operand: new Date(new Date().setFullYear(new Date().getFullYear() - 1)) },
// These should NOT be valid
{ field: 'name', operator: 'eq', operand: 5 },
{ field: 'age', operator: 'in', operand: 5 },
];
with these types:
interface Filterable {
name: string;
age: number;
joinDate: Date;
}
type NumberOperator = 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne';
type StringOperator = 'eq' | 'ne' | 'in' | 'ni' | 'sw' | 'ew';
type FilterOption = {
field: keyof Filterable,
operator: ???, // What type should I write here?
// NumberOperator type should apply for fields with number and Date types only.
// StringOperator type for fields with string type only.
operand: ???, // What type should I write here?
// This should be the type of the field as the key in Filterable interface.
}
Two questions:
operandtype - It seems to me like it is possible, but I don't know how to write it correctly. This is the closest solution I found, but I can't make it work for objects, this type of generics seems to work only with functions. How should I write generics correctly for this type?operatortype - I think this is much more complicated, and I don't know if it's even possible to accomplish. Is it possible?
Currently I'm using the following way to validate the configuration object. A bit ugly, but works:
interface FilterableString {
name: string;
}
interface FilterableNumber {
age: number;
}
interface FilterableDate {
joinDate: Date;
}
interface Filterable extends FilterableString, FilterableNumber, FilterableDate {}
type FilterOptionString = {
field: keyof FilterableString,
operator: StringOperator,
operand: string,
}
type FilterOptionNumber = {
field: keyof FilterableNumber,
operator: NumberOperator,
operand: number,
}
type FilterOptionDate = {
field: keyof FilterableDate,
operator: NumberOperator,
operand: Date,
}
type FilterOption = FilterOptionString | FilterOptionNumber | FilterOptionDate;
But again, I'm looking for a way to do it using generics, as in the future things might get even uglier and complicated, when more fields and types are added. This may become a bit of a headache to maintain, so I'm trying to avoid that.