Reputation: 5467
I'm struggling with inferring type based upon input parameters. Basically I want to use the structure of an object to infer the return type (although not just the . I think the trick is to use a conditional type but I can't get it to work:
type Active = { name: string; active: true };
type Inactive = { name: string; active: false };
type IdWithAct = Active | Inactive;
export const getIdWithType = <
Arg extends IdWithAct,
T extends Arg extends Active ? 'ActiveItem' : 'InactiveItem'
>({
name,
active,
}: IdWithAct): {name: string, typeName: T } => ({
name,
typeName: active ? 'ActiveItem' : 'InactiveItem',
});
See playground. I've looked at the infer
solutions but the examples are too trivial for what (at least I think) I'm trying to accomplish...
Added examples from the comments.
As suggested by the comments there is the overloading option, unfortunately TypeScript seems to have issues with calling the function within another:
type ActivityWithName<T> = { name: string, active: T}
type IdWithAct = ActivityWithName<true> | ActivityWithName<false>;
type Types = 'ActiveItem' | 'InactiveItem'
type Ret<T> = { name: string, typeName: Types }
function getFragNameAndIdFromActive(args: ActivityWithName<true>): Ret<'ActiveItem'>
function getFragNameAndIdFromActive(args: ActivityWithName<false>): Ret<'InactiveItem'>
function getFragNameAndIdFromActive<Arg extends IdWithAct>({
name,
active,
}: IdWithAct): Ret<Types> {
return {
name,
typeName: active ? 'ActiveItem' : 'InactiveItem',
}
}
const retActive = getFragNameAndIdFromActive({ name: 'activeItem', active: true })
const retInactive = getFragNameAndIdFromActive({ name: 'activeItem', active: false })
function test(active: boolean) {
// active complaints: Type 'boolean' is not assignable to type 'false'.
const ret = getFragNameAndIdFromActive({ name: 'activeItem', active })
}
See playground. The solution is appealing as it is rather explicit with the intentions of the overloadning.
The suggestion with the runtime assertion works but it is in my opinion a little too magic and if the return object is different by more than a single value it could very well become rather messy:
type IdWithAct<T> = { name: string; active: T };
type Status<T> = T extends true ? 'ActiveItem' : 'InactiveItem'
export const getIdWithType = <T extends boolean>({
name,
active,
}: IdWithAct<T>) => ({
name,
typeName: (active ? 'ActiveItem' : 'InactiveItem') as Status<T>,
});
const foo = getIdWithType({ name: 'x', active: true }) // { name: string; typeName: "ActiveItem"; }
const bar = getIdWithType({ name: 'x', active: false }) // { name: string; typeName: "InactiveItem"; }
const test = (active: boolean) => getIdWithType({ name: 'x', active })
Upvotes: 0
Views: 120
Reputation: 33041
You need to add one more overload:
type ActivityWithName<T> = { name: string, active: T}
type IdWithAct = ActivityWithName<boolean>
type Types = 'ActiveItem' | 'InactiveItem'
type Ret<T extends Types> = { name: string, typeName: T } // use generic parameter
function getFragNameAndIdFromActive<T extends true>(args: ActivityWithName<T>): Ret<'ActiveItem'>
function getFragNameAndIdFromActive<T extends false>(args: ActivityWithName<T>): Ret<'InactiveItem'>
function getFragNameAndIdFromActive(args: ActivityWithName<boolean>): Ret<Types> // less specific overload
function getFragNameAndIdFromActive({
name,
active,
}: IdWithAct): Ret<Types> {
return {
name,
typeName: active ? 'ActiveItem' : 'InactiveItem',
}
}
const retActive = getFragNameAndIdFromActive({ name: 'activeItem', active: true })
const retInactive = getFragNameAndIdFromActive({ name: 'activeItem', active: false })
function test(active: boolean) {
const ret = getFragNameAndIdFromActive({ name: 'activeItem', active }) // ok
}
Upvotes: 1