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...
Edit
Added examples from the comments.
Overloading
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.
Runtime assertion
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 })