An object literal that incorporates an interface featuring a generic method

My custom types and interfaces are as follows:

export type MultiMedia = 'image' | 'audio' | 'video';
export type FieldType = 'string' | 'number' | 'boolean' | MultiMedia;

export interface Field {
  name: string,
  label: string,
  type: FieldType,
  validator: <T>(val: T) => boolean,
  bounds: { lower: number; upper: number }
}

export interface Theme {
  title: string,
  logoPath: string,
  tags: string[],
  fields: Field[]
}

The field type varies between different objects, so I defined a generic method validator in the Field interface. However, TypeScript raises an error when creating an object literal that implements the Field interface.

Type '(val: string) => boolean' is not assignable to type '(val: T) => boolean.

const fields: Field[] = [
  {
    name: "firstName",
    label: "First Name",
    type: "string",
    bounds: { lower: 1, upper: 1 },
    validator: (val: string) => {
     return val.length > 20;
    }
  }

To resolve the issue, I modified the Field interface as shown below.

export interface Field<T> {
  name: string,
  label: string,
  type: FieldType,
  validator: (val: T) => boolean,
  bounds: { lower: number; upper: number }
}

However, TypeScript now complains about the fields property in the Theme interface.

Generic Type 'Field' requires 1 type argument(s).

Answer №1

It appears that the main issue at hand is with the Field, which essentially functions as a union. This means that it must be either a string field, or a number field, and so forth. One way to tackle this is by creating a generic interface called SpecificField<F> that enforces the constraint from each field type F to its validator. Then, define Field as a type alias for the union of all SpecificField<F> types:

Firstly, let's establish the field mapping to understand what the validator should accept.

interface FieldMapping {
  image: HTMLImageElement;
  audio: HTMLAudioElement;
  video: HTMLVideoElement;
  string: string;
  number: number;
  boolean: boolean;
}

Next, the SpecificField<F> interface is defined as follows:

export interface SpecificField<F extends FieldType> {
  name: string;
  label: string;
  type: F;
  validator: (val: FieldMapping[F]) => boolean;
  bounds: { lower: number; upper: number };
}

In essence, SpecificField<string> will have a type of "string" and a validator that only accepts string values. The next step involves making Field a union through the following method:

type Field = { [F in FieldType]: SpecificField<F> }[FieldType];

This utilizes a mapped type to acquire each field as a property and then combines them into a union by looking up all properties. You can confirm that it results in:

type Field = SpecificField<"string"> | SpecificField<"number"> |
  SpecificField<"boolean"> | SpecificField<"image"> | SpecificField<"audio"> | 
  SpecificField<"video">

Subsequently, you can test the functionality with the following snippet:

const fields: Field[] = [
  {
    name: "firstName",
    label: "First Name",
    type: "string",
    bounds: { lower: 1, upper: 1 },
    validator: (val: string) => {
      return val.length > 20;
    }
  }
];

I trust this explanation proves beneficial; wishing you success!

Link to code

Answer №2

Your previous solution appears to be almost correct, with the only adjustment needed being the declaration of the type T within the Field interface (both in the Theme interface definition and in the constant fields declaration), rather than when specifying the validator:

export type MultiMedia = 'image' | 'audio' | 'video';
export type FieldType = 'string' | 'number' | 'boolean' | MultiMedia;

export interface Field<T> {
  name: string,
  label: string,
  type: FieldType,
  validator: (val: T) => boolean,
  bounds: { lower: number; upper: number }
}

export interface Theme {
  title: string,
  logoPath: string,
  tags: string[],
  fields: Field<string>[]
}

const fields: Field<string>[] = [
  {
    name: "firstName",
    label: "First Name",
    type: "string",
    bounds: { lower: 1, upper: 1 },
    validator: (val: string) => {
     return val.length > 20;
    }
  }
]

Update:

In addition, if you wish to restrict the type of T to solely one of the FieldType options, you can implement the following modification:

export interface Field<T extends FieldType> {
  name: string,
  label: string,
  type: FieldType,
  validator: (val: T) => boolean,
  bounds: { lower: number; upper: number }
}

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

Discovering the power of chaining operators in RxJS 6 by encapsulating .map within

I am in the process of updating my Observable code from RXJS 5 to version 6. import { Injectable } from '@angular/core'; import { Observable } from 'rxjs' import { AppConfig } from '../config/app-config'; import { Xapi } from ...

A TypeScript function containing dual return statements

Consider the following code snippet: export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): { [key: string]: any } | null => { const forbidden = nameRe.test(control.value) return forbidden ? { ...

When invoking a JavaScript method, the context variable 'this' is lost

I have developed a basic pointer to a method like this: export class SmbwaService { getExistingArsByLab(labId: number): Observable<SmwbaAr[]> { this.otherMethod(); } otherMethod(): void { } } let method: (x: number) => ...

Unusual problem arises with scoping when employing typeguards

Consider the following TypeScript code snippet: interface A { bar: string; } const isA = <T>(obj: T): obj is T & A => { obj['bar'] = 'world'; return true; } let obj = { foo: 'hello' }; if (!isA(obj)) thro ...

Getting React, Redux, and Typescript all collaborating together

Recently, I made the transition from using Angular to learning React and Redux. My first challenge arose when trying to integrate Redux into my React project. I set up a simple state, action, reducer, and store in my index.tsx file: export interface AppSt ...

A guide on verifying the static characteristics of a class with an interface

When it comes to converting a constructor function in JavaScript to TypeScript, there are some important considerations to keep in mind. function C() { this.x = 100; } C.prototype = { constructor: C, m() {} }; C.staticM = function () {}; Here ...

International replacement of external interface exportation

Currently, I am utilizing the @react-native-firebase/messaging library and invoking the method messaging().onNotificationOpenedApp(remoteMessage => ....) I am aiming to replace the property data within the type of remoteMessage in order to benefit from ...

The Monorepo encountered an issue with parsing the module in NextJS 13 combined with Typescript, resulting in

Currently, I am in the process of transferring a functional nextjs 13 app to a single monorepo. I started by creating a new repository using npx create-turbo@latest and then relocating my existing repository (let's call it "frontend") to the apps/ dir ...

Extending Angular 2 functionality from a parent component

As a continuation of the discussion on Angular2 and class inheritance support here on SO, I have a question: Check out my plunckr example: http://plnkr.co/edit/ihdAJuUcyOj5Ze93BwIQ?p=preview Here is what I am attempting to achieve: I want to implement s ...

Error encountered during Svelte/Vite/Pixi.js build: Unable to utilize import statement outside of a module

I'm currently diving into a project that involves Svelte-Kit (my first venture into svelte), Vite, TypeScript, and Pixi. Whenever I attempt to execute vite build, the dreaded error Cannot use import statement outside a module rears its ugly head. Desp ...

Error: Identifier 'LibraryManagedAttributes' is already in use

I am facing a similar issue to: React typescript (2312,14): Duplicate identifier 'LibraryManagedAttributes' and TypeScript error: Duplicate identifier 'LibraryManagedAttributes' Despite upgrading to the latest node/npm/yarn/typescript v ...

React Redux not properly handling text input updates when onChange event is triggered

I have come across similar inquiries, but they haven't provided the solution I need. Currently, I am working on a React project where I am integrating redux. This is how my index.js looks: import React from "react"; import ReactDOM from "react-dom"; ...

What is the best way to implement asynchronous guarding for users?

Seeking assistance with implementing async route guard. I have a service that handles user authentication: @Injectable() export class GlobalVarsService { private isAgreeOk = new BehaviorSubject(false); constructor() { }; getAgreeState(): Obser ...

Using Typescript to pass a property as one of the keys in an object's list of values

In my React Native project, I need to pass a string value from one component to another. The different options for the value can be found in the ScannerAction object: export const ScannerAction = { move: 'move', inventory: 'inventory&apo ...

In an Electron-React-Typescript-Webpack application, it is important to note that the target is not a DOM

Rendering seems to be working fine for the mainWindow. webpack.config.js : var renderer_config = { mode: isEnvProduction ? 'production' : 'development', entry: { app: './src/app/index.tsx', //app_A: './src/a ...

Enhancing Security and Privacy of User Information with JWT Tokens and NgRx Integration in Angular Application

I'm facing a security concern with my Angular application. Currently, I store user details like isAdmin, isLoggedIn, email, and more in local storage. However, I'm worried about the risks of unauthorized updates to this data, especially since my ...

What is the process of defining an object enum in a declarations document?

Here is a sample javascript file named test.js: const someType = { val1: "myvalue", val2: "myothervalue" }; function sampleFunction(param) { return 1; } function sampleFunction2(param) { return 2; } export {someType, sampleFunction, sampleFunct ...

Encountering the error "TypeScript: Property 'FOO' does not exist on type" when trying to add a property to an object that has already been declared

Encountering an error in TypeScript: error TS2339: Property 'FOO' is not found in type '{ stuff ... 201 more ...; }'. Constants.FOO.forEach((item) => { ~~~ Arising from this scenario: // Constants.js const Constants = { ...

Exploring the method for obtaining parameters from a generic constructor

I have a customized class called Collection, which takes another class as a parameter named personClass. I expect the method add to accept parameters that are used in the constructor of the class User class Person { constructor(public data: object) { } ...

The not-null constraint is violated in the "id" column because of a null value when using Sequelize Typescript

My Database Setup Journey Recently, I embarked on a database adventure where I decided to use Sequelize-Typescript to assist me with all the heavy lifting in terms of database operations. The first step was creating a table called uma_tbl_users, and here ...