The compiler is unable to handle the "correlated union types" described in ms/TS#30581,
Instead of using assertion, which breaks the type-safety, or having a switch case for every type of filter, let's use the suggested approach described in ms/TS#47109.
First, we will need a type that would store all necessary type for filter:
type TypeMap = {
string: {
type: string;
extra: { customStringAttr?: any };
};
number: {
type: number;
extra: { customStringAttr?: any };
};
};
extra
field is used to add some additional fields that you actually need.
Now, let's recreate your filter types using mapped types:
type FilterObject = {
[K in keyof TypeMap]: {
type: K;
value?: TypeMap[K]['type'];
renderValue?: (val?: TypeMap[K]['type']) => string;
} & TypeMap[K]['extra'];
};
Testing:
type FilterObject = {
string: {
type: "string";
value?: string | undefined;
renderValue?: ((val?: string | undefined) => string) | undefined;
} & {
customStringAttr?: any;
};
number: {
type: "number";
value?: number | undefined;
renderValue?: ((val?: number | undefined) => string) | undefined;
} & {
...;
};
}
To actually get the array of filters we will use the ValueOf
described in this answer:
type ValueOf<T> = T[keyof T];
// (({
// type: "string";
// value?: string | undefined;
// renderValue?: ((val?: string | undefined) => string) | undefined;
// } & {
// customStringAttr?: any;
// }) | ({
// type: "number";
// value?: number | undefined;
// renderValue?: ((val?: number | undefined) => string) | undefined;
// } & {
// ...;
// }))[]
type Result = ValueOf<FilterObject>[]
const filters: ValueOf<FilterObject>[] = [
{
type: 'string',
value: 'something',
renderValue: (val) => `My string val --> ${val ?? 'none'}`,
},
{
type: 'number',
value: undefined,
renderValue: (val) => `My number val --> ${val?.toFixed(2) ?? 'none'}`,
},
];
For finishing the approach we will need a generic function that will accept a generic parameter constrained by keyof TypeMap
and the argument will be the whole filter under that key:
const render = <T extends keyof TypeMap>(arg: FilterObject[T]) => {
arg.renderValue?.(arg.value);
};
Usage:
filters.forEach((f) => {
render(f); // no error
});
playground