0

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 })
5
  • Only with type assertion because generics can be resolved only when calling the function, not within it typescriptlang.org/play?#code/… Commented May 8, 2021 at 7:53
  • Try to write overloads Commented May 8, 2021 at 10:13
  • @captain-yossarian - overloads look appealing but I can't get them to work properly, see my update to the question. Commented May 8, 2021 at 13:15
  • You may be facing this issue - stackoverflow.com/questions/56505560/… Commented May 8, 2021 at 14:03
  • W.R>T above comment you can check this - typescriptlang.org/play?#code/… Commented May 8, 2021 at 14:03

1 Answer 1

1

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
}

Playground

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.