Reputation: 7315
I have the following object (for testing only):
{
type: 'custom',
source: (): number[] => { return [1,2,3,4] },
filters: [
{
text: 'Is in one of',
value: 'iiof',
action: (v: MainType, k: number[]) => true
},
{
text: 'Is not in one of',
value: '!iiof',
action: (v: MainType, k: number[]) => true
}
]
}
And I'm trying to build the types, so that the second parameter of any filter.action
function, will be the return type of source
. I can use any
, but would prefer to have this typed if possible.
My current types are the following:
export type FilterCustomImpl<T, K> = {
text: string;
value: string;
action: (v: T, s: K[]) => boolean;
};
export type FilterCustomSchemaField<T, K = any> = {
type: 'custom',
display?: string;
source: () => K[];
filters: FilterCustomImpl<T, K>[]
};
export type FilterSchemaField<T> =
FilterEnumSchemaField |
FilterNumericSchemaField |
FilterStringSchemaField |
FilterCustomSchemaField<T>;
export type FilterSchemaFields<T> = { [index: string]: FilterSchemaField<T> };
export type FilterSchema<T> = {
defaultKey: string;
fields: FilterSchemaFields<T>
};
So the parent FilterSchema
could have an infinite number of FilterCustomSchemaField
s with different K
types.
Currently when building the filter schema I can get around it by specifying the type on the field itself, so
{
type: 'custom',
source: (): number[] => { return [1,2,3,4] },
filters: [
{
text: 'Is in one of',
value: 'iiof',
action: (v: MainType, k: number[]) => true
},
{
text: 'Is not in one of',
value: '!iiof',
action: (v: MainType, k: number[]) => true
}
]
} as FilterCustomSchemaField<MainType, number>
But it's not a great approach because it obviously requires extra typing, and has to rewrite the main T
type each time.
It would be ideal if I could infer the K
type from the return type of the source
function, but I'm not sure that's possible.
Upvotes: 0
Views: 47
Reputation: 327634
I'd probably use a curried helper function (the currying is to work around the lack of partial type parameter inference, more details in this answer) so that you can manually specify T
just once, and have the compiler infer the value of U
. (I know you called it K
, but the convention is that K
and P
are used for key types, whereas T
and U
are used for more general types... and since you're not constraining it to key types to something like string | number | symbol
I will change the name.)
Here it is:
const asFilterCustomSchemaField = <T>() => <U>(
x: FilterCustomSchemaField<T, U>
) => x;
And here's how it's used:
const filterCustomSchemaField = asFilterCustomSchemaField<MainType>()({
type: "custom",
source: () => [1, 2, 3, 4],
filters: [
{
text: "Is in one of",
value: "iiof",
action: (v, k) => true // v is inferred as MainType, k as number[]
},
{
text: "Is not in one of",
value: "!iiof",
action: (v, k) => true // v is inferred as MainType, k as number[]
}
]
});
// const filterCustomSchemaField: FilterCustomSchemaField<MainType, number>
Note how the value is of type FilterCustomSchemaFiled<MainType, number>
as desired, where number
is just inferred from the value, and MainType
is specified. If you use IntelliSense and hover over v
and k
in the action
properties, you will see they are inferred as MainType
and number[]
respectively, also as desired.
Okay, hope that helps; good luck!
Upvotes: 1