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 })
Max Gordon
  • 5,367
  • 2
  • 44
  • 70
  • Only with type assertion because generics can be resolved only when calling the function, not within it https://www.typescriptlang.org/play?#code/C4TwDgpgBAkgJgdQJbABYEEDGwA8AVAPigF4oBvKAOwEMBbCALigGdgAnJSgcwG4prsSAG6MoeKAF8eAKFCQoAZWDVgAV2b4ipcRAAewCJTjMo7VdAD8UAORZgwiDAO1rUJtZg1BIpxBfTpPTAAezZgKExgylYoLghgeGQ0PHBoUnwoPQMjEwAjYOCAGwhqSgIACjJpKBqqOggAGmragXsRJokmRJQMbE0AShIiSuaamnom2tNUgDl6pnLWhygrW29HZ1d3TyWfTcHqEyUVdU0O-plpSOjwgDMCklj47uTUyrr6d11rBv51pjM0AkgwA9CDyB9RKwONw+HIIHNPlAAER2By+WjIvgSK5RGK5ahsR5xBKIHopSDvcaiazfX67US3aiFZhA0HgijUpjQzi8aaQRGiZE7dYYrGSIA – Aleksey L. May 08 '21 at 07:53
  • @captain-yossarian - overloads look appealing but I can't get them to work properly, see my update to the question. – Max Gordon May 08 '21 at 13:15
  • You may be facing this issue - https://stackoverflow.com/questions/56505560/how-to-fix-ts2322-could-be-instantiated-with-a-different-subtype-of-constraint – MBB May 08 '21 at 14:03
  • W.R>T above comment you can check this - https://www.typescriptlang.org/play?#code/C4TwDgpgBAggxsAlgN2gXigbygOwIYC2EAXFAM7ABOiOA5gNxR4IolRUCu0AvvQFChI5eElRQM2ZqLace-QdACS+FmIm5CbCtTqMprUgDM8AGzJyB4JQBMA6omAALEeNiroAHyjL9qfnzgAexwKKFoIYAAxSjxaADlNGBxrRWtowIIRVlcAHj4oWEpaKAgAD2AIZLJvOwdnBAAafKgAFRLyyutqmCL2iqq3aSgAfigAcizURQqCMahSMZ93aYhZvgA+AApMZvwiJoLfCCbuUlT7JxEASlIdgr2tKhoGZoUEolI2rwnlmbnvpbSFazfjccTrKDbXaaA7sKzvNhHEbjSYQYFzBaA1jok5XfxBEKBEwQAB0JkCtE24SiMXiiWSqXSmXc2w0H3GswaTHcRlM5ig3CueL4QA – MBB May 08 '21 at 14:03

1 Answers1

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