Interface with several generic types

In an attempt to create a parser that can parse data fields and convert them into a complete form for display purposes, the fields property plays a crucial role. This property will define each field in a JSON data array that the client receives from the url property within the IForm.

An example of the form interface is outlined below:

export interface IForm {
    name: string;
    url: string;
    fields: IField<any>[] // <-- What should be the appropriate type for this?
}

// Additional interfaces defined...

The fields property within IForm can encompass multiple types like IField<string>, IField<number>, etc., depending on the value of the type property in each case, all derived from IField. As a result, it's uncertain whether using <any> is the best approach due to the various data types within the array. How should this be properly defined? Or is it better to avoid generics entirely and resort to using any?

A hypothetical set of data could resemble the following:

let meta: IForm = {
    name: 'employee',
    url: '/api/employee',
    fields: [
        // Sample fields defined..
    ]
}

In relation to this, an employee interface might look like:

export interface IEmployee {
    id: number;
    name: string;
    gender: string;
    active: boolean;
    department: number;
}

How should the interface for IForm be designed to accommodate this structure efficiently?

Thank you

Answer №1

In light of the aforementioned information, I recommend implementing something along these lines:

type PossibleDataTypes = string | number | boolean; // or any other types you desire

type PossibleFields =
  | IInput<PossibleDataTypes>
  | IOptions<PossibleDataTypes>
  | ICheckbox;

export interface IForm {
  name: string;
  url: string;
  fields: Array<PossibleFields>;
}

This approach specifically defines the array of fields to encompass only the expected field types. You have the flexibility to expand this list as needed.

Additionally, a modification was made as follows:

// Updated from IField<IOptionsList<T>> to simply IOptionsList<T>
export interface IOptions<T> extends IField<T> {
  type: "options";
  options?:
    | IOptionsUrl
    | ReadonlyArray<IOptionsList<T>>
    | ReadonlyArray<string>;
  multiple?: boolean;
}

This change was made due to an inconsistency in your meta variable without it. Moreover, there seems to be a small error in using 'data' instead of 'url' within meta. Whilst defining meta as an IForm is acceptable, widening a variable to IForm may overlook specifics (such as the specific field types being utilized). In case you solely wish to validate that meta aligns with IForm sans broadening it to IForm, employing a helper function like the following is suggested:

const asIForm = <F extends IForm>(f: F) => f;

This can then be employed as shown below:

const meta = asIForm({
  name: "employee",
  url: "/api/employee",
  fields: [
    {
      type: "input",
      name: "id",
      label: "Employee ID",
      default: 0,
      mandatory: true
    },
    {
      type: "options",
      name: "gender",
      label: "Male/Female",
      default: "male",
      options: ["Male", "Female"]
    },
    {
      type: "checkbox",
      name: "active",
      label: "Active",
      default: true
    },
    {
      type: "options",
      name: "department",
      label: "Department",
      default: 0,
      options: {
        url: "/api/departments",
        idField: "id",
        labelField: "name"
      }
    }
  ]
});

With PossibleFields representing a tangible discriminated union, the compiler can subsequently narrow each entry in the field via a type guard, exemplified as follows:

function processForm(form: IForm) {
  for (let field of form.fields) {
    switch (field.type) {
      case "input": {
        // action for input
        break;
      }
      case "checkbox": {
        // action for checkbox
        break;
      }
      case "options": {
        // action for options
        field.options // <-- no error, known to exist
        break;
      }
      default:
        ((x: never) => console.log("WHAT IS" + x))(field); // ensure exhaustive
        // If an error appears here -------------> ~~~~~
        // then you missed a case in the switch statement
    }
  }
}

Wishing you success and hoping this information proves beneficial!

Link to code

Answer №2

To restrict the types allowed, you can employ a Type Union:

type formType = string | number | boolean | date;
export interface IForm {
  name: string;
  url: string;
  fields: IField<formType>[] // <-- What should be the correct type for this?
}



let form.default = "adsf";     //valid
let form.default = 1;          //valid
let form.default = true;       //valid
let form.default = new date(); //valid
let form.default = null;       //invalid
let form.default = undefined;  //invalid
let form.default = never;      //invalid

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

Ensuring that Vue3 Typescript app focuses on input element within Bootstrap modal upon opening

I am facing a challenge with setting the focus on a specific text input element within a modal dialog. I have tried various methods but none seem to achieve the desired outcome. Here is what I have attempted: Attempt 1: Using autofocus attribute. <inpu ...

Accessing the currently operating WS server instance with NodeJS

After successfully setting up a basic REST API using NodeJS, ExpressJS, and routing-controllers, I also managed to configure a WebSocket server alongside the REST API by implementing WS. const app = express(); app.use(bodyParser.json({limit: "50mb"})); a ...

TS - deduce the specific type of a key value without receiving a union type

Welcome to the coding playground: Click here to start coding Let's talk about a scenario where a function is expected to return some value based on an input argument. The challenge arises when there are keys with the same name but different types re ...

Ways to receive one of two variations

Dealing with different cases for type is my current challenge. In a React Functional Component, I have a callback function property that accepts an argument of either string or number. interface InputProps { getValue?: (value: string | number) => vo ...

Ways to utilize multiple tsconfig files within VS Code

My project structure in Visual Studio Code is fairly common with a client, server, and shared directory setup: ├── client/ │ ├── tsconfig.json ├── shared/ ├── server/ │ ├── tsconfig.json ├── project.json The tw ...

Explaining the union type using a list of data types

Is there a way to create a union type that strictly limits values to 'a', 'b', 'c' when using a list like const list: string[] = ['a', 'b', 'c']? I know one method is: const list = ['a' ...

Is it not possible to access the width and height of an element using ViewChild, unlike what is suggested in other responses

I've encountered an issue with my Angular component. The HTML code for the component, before any TypeScript is applied, looks like this: <svg #linechart id="chartSpace"></svg> Wanting to create a responsive webpage featuring a line chart ...

Organize nested tuples and maintain their sequence

I am attempting to achieve a functionality similar to the following: type Type = object; type TypeTuple = readonly Type[]; function flattenTuples<T extends readonly (Type | TypeTuple)[], R = Flatten<T>>(...tuples: T): R { // code to flatten ...

Is searching for duplicate entries in an array using a specific key?

Below is an array structure: [ { "Date": "2020-07", "data": [ { "id": "35ebd073-600c-4be4-a750-41c4be5ed24a", "Date": "2020-07-03T00:00:00.000Z", ...

Challenges with variable scopes and passing variables in Ionic 2 (Typescript)

In my Ionic 2 TypeScript file, I am facing an issue with setting the value of a variable from another method. When I close the modal, I get undefined as the value. I'm encountering difficulty in setting the value for coord. export class RegisterMapP ...

Error in vue3 with typescript: unable to assign a ComputeRef<number[]> argument to an iterable<number> in d3.js

This code snippet was originally sourced from an example at https://medium.com/@lambrospd/5-simple-rules-to-data-visualization-with-vue-js-and-d3-js-f6b2bd6a1d40 I attempted to adapt the example to a TypeScript/Vue 3 version, and below is my implementatio ...

Tips for Sending Specific Row Information to Server in Ionic 3

Successfully pulled contacts from the device into my list page, but struggling to send specific contact details (name and number) to the server. Can anyone provide guidance on accomplishing this in Ionic? ...

How to Send Data with NodeJS by Utilizing the Finish Event

Is there a way to retrieve the JSON data sent during the nodejs finish event? This is how I send the JSON data: oResponse.json({ version: "1.0.0", author: "Someone", contributors: "also Someone" }); I would like ...

Invoke the built-in matcher within a Playwright custom matcher

I am in the process of implementing a custom matcher for Playwright based on the information provided in the official documentation on extending expect. In a similar vein to this unanswered discussion, my goal is to invoke an existing matcher after modifyi ...

Is it considered appropriate to return null in a didReceiveResponse callback function?

In my implementation, I have a callback called didReceiveResponse within a class that extends RESTDataSource. In this method, I return null when the response status is 404. However, due to the typing definition of RESTDataSource.didReceiveResponse, it seem ...

A step-by-step guide on bundling a TypeScript Language Server Extensions LSP using WebPack

Currently, I am working on a language server extension for vs-code that is based on the lsp-sample code. You can find the code here: https://github.com/microsoft/vscode-extension-samples/tree/master/lsp-sample My challenge lies in WebPacking the extension ...

Cannot assign an array of Typescript type Never[] to an array of objects

Creating an object array can sometimes lead to errors, let's take a look at an example: objectExample[a].push({ id: john, detail: true }); objectExample[a].push({ id: james, detail: false}); const objectExample = { a = [ { id: john, detail: true} ...

Best practice for importing pipeable RxJs operators in Angular CLI/WebPack rollup

In the earlier versions of Angular CLI, specifically those before 1.5.0, it was common practice to import all RxJs operators and statics into a single file for easy usage throughout the application. For example: rxjs-operators.ts // Statics import &apos ...

Place the setState function within the useEffect hook

I'm currently working on a project that includes a "login" page. Once the user logs in, they should be directed to an interface displaying all their lists. To ensure this data loads immediately after login, I have implemented the useEffect hook and u ...

Preventing going back to a previous step or disabling a step within the CDK Stepper functionality

In my Angular application, there is a CdkStepper with 4 steps that functions as expected. Each step must be completed in order, but users can always go back to the previous step if needed. For more information on CdkStepper: https://material.angular.io/cd ...