Guidelines for allowing TypeScript to automatically determine the precise structure of data objects in a generic HttpServiceMock through the utilization of TypeScript Generics and Interfaces

I'm currently diving into TypeScript and trying to accomplish something that I'm not entirely sure is possible (but I believe it is).

Here's an example of the code I have:

interface HttpServiceMockData<T> {
  status: number;
  data: T;
  url: string;
}

export function createHttpServiceMock<T>(data: HttpServiceMockData<T>[]) {
  return {
    get: async (url: string): Promise<{ data: T }> => {
      const res = data.find((d) => d.url === url);
      if (!res) {
        throw new Error(`No data found for url ${url}`);
      }
      return {
        data: res.data,
      };
    },
  };
}

const service = createHttpServiceMock([
  {
    url: '/users/1',
    data: {
      id: 1,
      username: 'test',
    },
    status: 200,
  },
  {
    url: 'test',
    data: {
      id: 1,
      username: 'test',
      lastname: 'test',
    },
    status: 200,
  },
]);

const res = service.get('test').then((res) => {
  res.data // I wish for TypeScript to accurately recognize the type here (inference power)
});

Currently, TypeScript is aware of what needs to be returned but lacks precise information about the structure of the data object, hence providing "lastname" as optional, which doesn't align with my requirements.
I intend to input the URL and have TypeScript automatically understand my desired output.

Any thoughts on how to achieve this?

Answer №1

A great use case for Typescript is showcased in scenarios like this, where its strengths truly shine. However, it does require some guidance from your end to fully leverage its potential.

Why not test it out in the TS Playground.

export function createHttpServiceMock<Services extends HttpServiceMockData<any>>(
  data: ReadonlyArray<Services>
) {
  return {
    get: async <TargetUrl extends Services['url']>(url: TargetUrl)
        : Promise<{ data: (Services & { url : TargetUrl })['data'] }> => {
      //...
    },
  };
};

To break it down, a discriminated union type (Services) is constructed from the array to represent your services. The url parameter in the returned get function is restricted to the url property of the provided services. It is set up as a generic so it can be utilized as a type variable. The return type of get chooses the appropriate service type by intersecting the Services union with an object type containing the specified url property, followed by selecting its corresponding data property.

Furthermore, when invoking your services, make sure to declare the url properties (or the service) with as const to ensure that Typescript interprets the url tags literally, rather than as conventional strings.

const service = createHttpServiceMock([
  {
    url: '/users/1' as const,
    data: {
      id: 1,
      username: 'test',
    },
    status: 200,
  }, // or
  {
    url: 'test',
    data: {
      id: 1,
      username: 'test',
      lastname: 'test',
    },
    status: 200,
  } as const,
]);

Alternatively, if you define your services in a table structure, you can simplify the process without the need for union intersection:

type ServiceTable = { [K in string] : HttpServiceMockData<any> };
export function createHttpServiceMockTable<Services extends ServiceTable>(
  data: Services
) {
  return {
    get: async <TargetUrl extends keyof Services>(url: TargetUrl)
        : Promise<{ data: Services[TargetUrl]['data'] }> => {
      //...
    },
  };
};

const service2 = createHttpServiceMockTable({
  ['/users/1'] : {
    //...
  },
  test : {
    //...
  },
});

For a deeper understanding, refer to the Typescript Handbook, particularly the section on Objects.

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

Tips for populating an array with information gathered from an axios request within a React application

Currently, I am using axios to fetch data from an API and attempting to store the retrieved data in a variable that represents an array within the state of the component. However, when I try to do so, I encounter the following error: Objects are not valid ...

Material-UI icons refusing to show up on the display

I've been working on creating a sidebar component and importing various icons to enhance the UI, but for some reason they are not displaying on the screen. I even tried other suggested solutions without success. <SidebarOption Icon = {InsertComment ...

Utilizing a jQuery variable within an .html() method

Can a Jquery if statement be used to display different content inside a div based on a variable? For example, if the variable is set to "cats", the displayed content might say "I like cats", and if it changes to "dogs", it would read "I like dogs". Is this ...

Blend the power of Node's CommonJS with the versatility of Typescript's ES modules

I currently have a Node.js v10 legacy application that was built using CommonJS modules (require). The entire codebase is written in JavaScript. However, I am considering upgrading the app and refactoring a specific part of it to use TypeScript modules ( ...

What is the process for utilizing JEST to test a REST API built with Express?

Is it possible to solely use JEST for testing my rest API endpoints in express? I've undertaken various articles and browsed through questions on Stack Overflow to explore how this can be achieved. However, it seems like most individuals prefer using ...

What is the best way to implement the settimeout method in react?

I need assistance on how to effectively utilize the setTimeout() method in my code. Specifically, I am looking to trigger a click event on an element after a certain delay and execute a function afterwards. Here is the current implementation of my code: ...

Tips for toggling the display of multiple ion-input fields based on the selected value from an ion-select dropdown

I am working with an ion-select element that contains options from 1 to 10. <ion-label> Select how many input fields</ion-label> <ion-select> <ion-option value="0"> Zero</ion-option> <ion-option value="1"> One</ion- ...

What could be causing the malfunction in one of the functions within my JavaScript code?

As a JavaScript beginner, I am currently working on creating a To-do App with JavaScript. Most of the functions are functioning perfectly except for one called doneTask at line 36. Despite numerous attempts to identify the issue, I have been unsuccessful s ...

Is there a way to design a catalog page for my website that doesn't include a shopping cart or detailed product pages?

I am looking to create a catalogue feature similar to Newegg's for my website, but with a simplified approach. I have never attempted something this advanced before and I am wondering if it is possible to achieve. My plan is to use PHP and JS for the ...

When using Express, the XML response is returning an empty document

I'm experimenting with a simple API that returns XML response: const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const libxmljs = require("libxmljs"); const PO ...

Memory leaks are occurring due to the texture from the video tag

Currently, I am working on creating a texture in THREE.js (r78) from a video tag and updating the texture 60 times per second by setting needsupdate=true in requestanimationframe. However, I am facing a problem where I notice memory leakage in the Chrome T ...

Error encountered when transitioning to TypeScript: Unable to resolve '@/styles/globals.css'

While experimenting with the boilerplate template, I encountered an unusual issue when attempting to use TypeScript with the default NextJS configuration. The problem arose when changing the file extension from .js to .tsx / .tsx. Various versions of NextJ ...

The function is trying to access a property that has not been defined, resulting in

Here is a sample code that illustrates the concept I'm working on. Click here to run this code. An error occurred: "Cannot read property 'myValue' of undefined" class Foo { myValue = 'test123'; boo: Boo; constructor(b ...

Encountering an issue with React where the useContext hook is returning undefined instead of

I am facing an issue where I am attempting to access state and setState from the Store file, but it returns as undefined. Can someone help me understand what is causing this problem and how I can resolve it? import React, {createContext, useState} from &ap ...

filtering an array based on a specific property will result in the original array remaining

Working on filtering an array of objects based on a certain property using the following code snippet: if (payment == Payment.CREDIT_CARD) { this.currenies.filter((currency: Currency) => currency.isFromEurope === true); console.log(this.currencies) ...

The issue lies with Express Mongoose failing to store the data

Encountering some issues when trying to save an object created in Express nodejs using mongoose. Despite receiving a confirmation that the object is saved, it cannot be located even after attempting to access it through the server. Express route for savi ...

How can I use Angular 4 typescript to deactivate a button based on the value of a boolean variable?

I am looking to define a function in TypeScript called 'isActive()', which I will then invoke on a button within my HTML file. Additionally, I have already declared a boolean variable named 'isActive'. In this scenario, I have two butto ...

What is the best way to manage numerous asynchronous post requests in AngularJS?

$scope.savekbentry = function (value) { console.log('save clicked'); console.log(value); console.log($scope.kbentry.kbname); $scope.kbentry.mode = value; var kbname = $scope.kbentry.kbname; var kbd ...

Achieving full child div coverage within a bordered parent div on Chrome

I am encountering an issue where I am trying to insert an image into a bordered div, but in Chrome 96.0 there are unexpected margins appearing from the top and left that I cannot seem to remove. Below is the code I am using: .wrapper { width: 36px; ...

Managing data in React and JavaScript: Clearing fields upon successful sign up and redirecting to login page after receiving a 200 status code

Here is the code for my SignUp react function. After receiving a response of 200 upon clicking the Sign up button, I want to clear the text in all three fields and redirect the user back to the login page. I'm new to web development so any assistance ...