typescript - Function return type based on input parameters - Stack Overflow

In a simplified scenario I have the types:type Obj = {one: number;two: number;three?: number;four?: nu

In a simplified scenario I have the types:

type Obj = {
    one: number;
    two: number;
    three?: number;
    four?: number;
    five?: number;
}

type ObjOptional = {
    three?: boolean;
    four?: boolean;
    five?: boolean;
}

I'm using Axios to fetch remote data with something like the following:

function GetRemoteObj(id: number, whatToInclude: ObjOptional) {
    return axios.get<Obj>(`api/obj/${id}`, { params : whatToInclude });
}

Does typescript allow to define GetRemoteObj in a way that returned type is dependend to the parameter whatToInclude?

Example 1:

const obj = (await GetRemoteObj(1, { three: false, four: true })).data;

here obj type will be

{
    one: number;
    two: number;
    four: number;
}

Example 2:

const obj = (await GetRemoteObj(1, { })).data;

here obj type will be

{
    one: number;
    two: number;
}

Example 3:

const obj = (await GetRemoteObj(1, { three: true, four: true, five: true })).data;

here obj type will be

{
    one: number;
    two: number;
    three: number;
    four: number;
    five: number;
}

In a simplified scenario I have the types:

type Obj = {
    one: number;
    two: number;
    three?: number;
    four?: number;
    five?: number;
}

type ObjOptional = {
    three?: boolean;
    four?: boolean;
    five?: boolean;
}

I'm using Axios to fetch remote data with something like the following:

function GetRemoteObj(id: number, whatToInclude: ObjOptional) {
    return axios.get<Obj>(`api/obj/${id}`, { params : whatToInclude });
}

Does typescript allow to define GetRemoteObj in a way that returned type is dependend to the parameter whatToInclude?

Example 1:

const obj = (await GetRemoteObj(1, { three: false, four: true })).data;

here obj type will be

{
    one: number;
    two: number;
    four: number;
}

Example 2:

const obj = (await GetRemoteObj(1, { })).data;

here obj type will be

{
    one: number;
    two: number;
}

Example 3:

const obj = (await GetRemoteObj(1, { three: true, four: true, five: true })).data;

here obj type will be

{
    one: number;
    two: number;
    three: number;
    four: number;
    five: number;
}
Share Improve this question asked Jan 31 at 10:30 c.bearc.bear 1,44513 silver badges25 bronze badges 1
  • 1 I think this is what you're looking for. – HairyHandKerchief23 Commented Jan 31 at 11:40
Add a comment  | 

1 Answer 1

Reset to default 3

Yes, you can define the function types to satisfy your use case, since the possibilities are more continuous than discrete; you could have many keys and they follow a similar pattern, then generics is a good approach.

If you had only a few options you could use function overloads which let you provide types for distinct function signatures.

To use generics, you will also need to formulate the type of the return value according to the generic, you can use mapped types for this.

I am guessing from your question that you want the function to always return an object that satisfies Obj and also satisfies some type derived from ObjOptional where keys with a true value should be included with a number value. This will be the core of the mapped type.

A simple first approach can be to say given a type that satisfies ObjOptional, produce a type with the same keys and the value type number if input value is true and never if input key is false or undefined. So the generic bound is ObjOptional. This look like:

type MapToReturn<T extends ObjOptional> = {
  [K in keyof T]: T[K] extends true ? number : never;
};

{[K in keyof T]:.. takes each key from T as the keys for the resulting type, and provides you with K to work with on the value type. T[K] extends true ? number : false is a simple conditional type that returns number if the value extends true which in this case is true since except any only true extends true. And if not it returns never.

We can test this with the inputs from your first example:

MapToReturn<{ three: false; four: true }>

The type we get is { three: never; four: number;} This is a good start but not very helpful in practice since we cannot reasonably produce an object with key three but value never. However mapped types also allow you to remap keys using the as keyword which allows us the move the conditional logic from the value onto the key. We can then write the type as:

type MapToReturn<T extends ObjOptional> = {
  [K in keyof T as T[K] extends true ? K : never]: number;
};

In this case, if the input value at key K is not true the key will be dropped from the resulting type. And for the same input as before { three: false; four: true } we get type { four: number; }.

Next we should include Obj in the response. A simple way of doing this is to produce an intersection & with Obj.

type MapToReturn<T extends ObjOptional> = Obj & {
  [K in keyof T as T[K] extends true ? K : never]: number;
};

This works but since Obj defines values one, two as well as three to five optionally, we lose the benefits of getting back exact types, so to solve this we can introduce a new type, ObjRequired that includes only the keys that are always present, and use this to intersect with our mapped type.

type ObjRequired = {
  one: number;
  two: number;
};

type MapToReturn<T extends ObjOptional> = ObjRequired & {
  [K in keyof T as T[K] extends true ? K : never]: number;
};

This will now produce a type that works as expected, but having an intersection type is not always ideal to work with compared to a simple object like type. So instead of producing an intersection we can further refine our mapped type to include both the keys from ObjRequired and those true value entries from our generic:

type MapToReturn<T extends ObjOptional> = {
  [K in keyof T | keyof ObjRequired as K extends keyof T
    ? T[K] extends true
      ? K
      : never
    : K]: number;
};

The approach is mostly similar, we create a union | between the keys of our generic and those from ObjRequired. Because of this we have to check the key, K belongs to our generic T before indexing into its value T[K] to check whether it is true. This is why we have a nested conditional. We can leave the value type as number if we don't need to perform any further checks or operations on it.

The last thing is to introduce the generic into the function, this is fairly straightforward, if you don't mind the implicit return type you can type the generic on the axios get call as the return type we've built:

function getRemoteObj<T extends ObjOptional>(id: number, whatToInclude: T) {
  return axios.get<MapToReturn<T>>(`api/obj/${id}`, {
    params: whatToInclude,
  });
}

Hope that helps, here's a link to a typescript playground.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745268151a4619584.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信