What is the best way to utilize Typescript in developing a versatile API SDK that can dynamically determine the return type based on a specified string literal parameter?

Is there a way to use a pre-defined map of string to object types to determine both the return value and ensure type safety in an implemented function?

Let's take a look at this example:

import Axios from "axios";

export const axios = Axios.create();

type Foo = {
    id: string;
    name: string;
    foo: number;
}

type Bar = {
    id: string;
    name: string;
    bar: number;
}

type FooResponse = {
  response: Foo[];
}

type BarResponse = {
  response: Bar[];
}

type FooBarMap = {
  foo: FooResponse;
  bar: BarResponse;
}


const getFooBar = async <T extends keyof FooBarMap>(endpoint: T, query: Record<string, any> = {}) => {
  const records: FooBarMap[T]["response"] = [];

  const response = await axios.get<FooBarMap[T]>(`/${endpoint}`, query);

  const data = response.data.response;

  records.push(...data);

  return records;
}

Playground

This scenario triggers the following error:

Argument of type 'Foo | Bar' is not assignable to parameter of type 'Foo & Bar'. Type 'Foo' is not assignable to type 'Foo & Bar'. Property 'bar' is missing in type 'Foo' but required in type 'Bar'.

Answer №1

The compiler is encountering difficulties with the concept of "correlated union types" as mentioned in ms/TS#30581. However, a proposed solution can be found in ms/TS#47109.

First and foremost, it is necessary to define the following type:

type TypeMap = {
  foo: Foo
  bar: Bar
}

Subsequently, the FooBarMap should be transformed into a mapped type, capable of accepting the key from TypeMap as an optional generic parameter defaulted to all keys within TypeMap, returning the corresponding response for the specified key:

type FooBarMap<T extends keyof TypeMap = keyof TypeMap> = {
  [K in T]: { response: TypeMap[K][] }
}[T]

Finally, there needs to be a modification in how the provided generic is employed in the function getFooBar:

const getFooBar = async <T extends keyof TypeMap>(
  endpoint: T,
  query: Record<string, any> = {},
) => {
  const records: FooBarMap<T>['response'] = [];

  const response = await axios.get<FooBarMap<T>>(`/${endpoint}`, query);

  const data: FooBarMap<T>['response'] = response.data.response;

  records.push(...data); // no error

  return records;
};

playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Modifying the delimiter used in paste range feature of ag-grid

Is there a way to enable pasting tab separated data into an ag-grid column instead of a row? Currently, when pasting newline separated data it goes into columns and tab separated goes into rows. The documentation suggests using the "clipboardDeliminator" ...

Tips on implementing mongoose 'Query<any>' types

I have been exploring ways to incorporate a cache layer into my TypeScript project. Recently, I came across an insightful article on Medium titled How to add a Redis cache layer to Mongoose in Node.js The author demonstrates adding a caching function to m ...

Set a field to mandatory or optional depending on a condition in serenity-platform

Currently, I am tackling a project on the serenity platform. Does anyone have suggestions on how to dynamically change the field's required status based on a condition within the AbcDialog.ts file? Thank you! ...

What is the best way to filter an array of objects and store the results in a new variable list using typescript?

On the lookout for male objects in this list. list=[ { id: 1, name: "Sam", sex: "M"}, { id: 2, name: "Jane", sex: "F"}, { id: 3, name: "Mark", sex: "M"}, { id: 4, name: "Mary, sex: "F& ...

Retrieve user roles from core in Angular prior to implementing a guard

Hey, I'm facing an issue with my app.module setup. I have implemented lazy loading to load the core module and store roles in the core component. Everything works fine when I navigate from one page to another with the role guard applied. However, when ...

Differences between Typescript Generics and Unions

When I define a function type with parameters like value: string | number, I am able to define a function (value: string) => null of that type without TypeScript showing any warnings. However, if I define a function type with a generic parameter like & ...

Getting the current page name within the app.component.ts file in Ionic 3 - a simple guide

Is it possible to retrieve the current active page name within the app.component.ts file in Ionic without having to add code to any other pages? ...

Returning a 'never' type from a function in React using Typescript

Basically, I have a function that initiates the OAuth flow (redirecting to Google OAuth login page, for example): async function signIn() { // start OAuth flow } And let's say I want to use it in a useEffect hook like this: ... useEffect(() => { ...

I am experiencing an issue in Angular 6 where the close button is unresponsive when I add a carousel within a

I successfully added a carousel in a modal popup and it is working properly. However, I encountered an issue when trying to close the modal using the close button - it is not functioning. Here is a link to the stackblitz demo for reference. You can also ...

Modify the parent component using dialogRef while keeping the dialog open

Within my application, there is a child dialog connected to a parent component. The parent component contains a MatTable that gets updated when the "update" button in the child dialog is clicked. The following code in the parent component is functioning p ...

I have encountered a problem where I am trying to post a URL using a REST API call. This functionality was working perfectly fine in Angular 1.6, but I am facing

When trying to post a URL with Angular 7 to the server, I encountered an error stating "blocked by CORS policy," although it worked fine in Angular 1.6. The issue is related to CORS policy blocking access to XMLHttpRequest. The response to preflight reque ...

TypeScript enables the use of optional arguments through method overloading

Within my class, I have defined a method like so: lock(key: string, opts: any, cb?: LMClientLockCallBack): void; When a user calls it with all arguments: lock('foo', null, (err,val) => { }); The typings are correct. However, if they skip ...

Some of the compiler options that are not commonly known are those for including and

Embarking on my first typescript-node-express project, I decided to create my own tsconfig file with the following configuration: { "compilerOptions": { "target": "es6", "module": "commonjs", "strict": true, "baseUrl": "./", "outDi ...

Making changes to a property within a class does not automatically reflect those changes in the corresponding view

In my Typescript (.ts) file, this is the code I have: private today: Date = new Date(); And this is the corresponding HTML: <span [innerText]="today | date:dateFormat"></span> Everything displays perfectly, showing 22nd May. Now, I&apos ...

Sending Functions as Props in React Using Typescript from a Parent Component to a Child Component

Trying to pass props from Parent to Child using TypeScript-React but getting an error: "Type 'void' is not assignable to type 'Function'." Parent import React from "react"; import Navbar from "./navbar"; import Main from "./main"; f ...

How to properly import a new typings file in Typescript for Node.js applications?

I'm feeling quite overwhelmed by the different methods available for importing a Typings file. It seems like there are numerous ways to accomplish this task. Currently, I am working on a nodejs program. I successfully installed momentJS through typi ...

Locating a class variable using a string chosen from a DropDown menu

In my Tv class, I have several string variables. One requirement is for the user to select an option from a DropDown list and input a value. This entered value should then be stored in the Tv class under a variable with a similar name to the selected optio ...

What method is most effective for combining two JSON files in Angular?

My data includes a json file with a product list that looks like this: [{"id":76, "name":"A", "description":"abc", "price":199, "imageUrl":"image.jpg", "productCategory":[{ "categoryId":5, "category":null },{ "categoryId":6, " ...

Defining assertions with specified type criteria

Looking to do something special in TypeScript with a class called Foo. I want to create a static array named bar using const assertion, where the values are restricted to the keys of Foo. This array will serve as the type for the function func while also a ...

Iterate and combine a list of HTTP observables using Typescript

I have a collection of properties that need to be included in a larger mergeMap operation. this.customFeedsService.postNewSocialMediaFeed(this.newFeed) .mergeMap( newFeed => this.customFeedsService.postFeedProperties( newFeed.Id, this.feedP ...