1

I have a scenario where I want to have a function that can accept any number of args of an generic object.

I want the result to return a tuple where each of the generic params of this object is the tuple position type.

Example

type Wrap<T> = {
    obj: T;
}

function UnWrap<T extends Array<Wrap<One|Two>>>(...args:T){ //return type?
    return args.map(i => i.obj);
}

type One = {
    foo: string;
}

type Two = {
    bar: string;
}

let one: Wrap<One> = {obj: {foo: 'abc'}}

let two: Wrap<Two> ={obj: {bar: 'abc'}}

// res type should be [One, Two, Two, One]
let res = UnWrap(one, two, two, one) 

I can get the type to work if I just return the exact type passed in:

function ReturnSelf<T extends Array<Wrap<One|Two>>>(...args:T): typeof args{
    return args;
}

But I'm not sure how to index the ['obj'] type. I think maybe mapped types can be used to do this, but I can't quite figure it out.

Typescript Playground link

NSjonas
  • 10,693
  • 9
  • 66
  • 92
  • I am a bit confused about the problem statement. At runtime there are no types or interfaces, so afaik the type needs to be part of the generic type (could be part of the interface contract). Then you could simply map out the output in a tuple more or less as you are doing already. – cYrixmorten Sep 16 '19 at 18:02
  • Related issue: https://stackoverflow.com/questions/51672504/how-to-map-a-tuple-to-another-tuple-type-in-typescript-3-0 – NSjonas Sep 16 '19 at 18:02
  • @cYrixmorten not sure I understand what you mean... I'm trying to create something similar (not in domain) to how reselects "createSelector" works. https://github.com/reduxjs/reselect – NSjonas Sep 16 '19 at 18:05
  • 1
    Nevermind, seems I simply did not capture the nature of what you tried to accomplish. – cYrixmorten Sep 16 '19 at 18:08

1 Answers1

2

Yes, you can use mapped types to do this, ever since TypeScript 3.1 introduced the ability to map tuple/array types. You could either do it the "forward" way, like you were doing:

function UnWrap<T extends Array<Wrap<One | Two | Three>>>(...args: T) {
  return args.map(i => i.obj) as {
    [K in keyof T]: T[K] extends Wrap<infer U> ? U : never
  };
}

let res2 = UnWrap(one, two, three, two); // [One, Two, Three, Two]

or the "reverse" way, using inference from mapped types:

function UnWrap2<T extends Array<One | Two | Three>>(
  ...args: { [K in keyof T]: Wrap<T[K]> }
) {
  return args.map(i => i.obj) as T;
}
let res3 = UnWrap2(one, two, three, two); // [One, Two, Three, Two]

Either way will work, as you can see... and either way, the compiler won't be able to understand that args.map(i => i.obj) performs the type manipulation you're doing, so you'll need to use a type assertion or the equivalent of one (such as using a single overload signature).

Okay, hope that helps. Good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • beautiful. I realized my example was a little off, as the types of "obj" are actually not known ahead of time. However, I was able to replace `Wrap` with `Wrap` and to my surprise, it still works! Typescript is amazing – NSjonas Sep 16 '19 at 18:25