Generic TypeScript any object that is plain

update I have included my current code to better illustrate the problem:

https://gist.github.com/LukasBombach/7bf255392074509147a250b448388518

Using TypeScript, I aim to define a generic that represents any plain object data structure

class MyClass<T extends {}> {
  public vars: T;

  constructor() {
    this.vars = {};
  }
}

which allows me to use it like this

interface MyType {
  foo: string;
  bar: number;
}

new MyClass<MyType>()

or like this

interface MyType {
  baz: string;
  boo: {
    bam: Function;
    bix: number;
  };
}

new MyClass<MyType>()

However, the implementation I have shown above is not functioning as expected, and I am encountering this error:

class MyClass<T extends {}> {
  public vars: T;

  constructor() {
    this.vars = {};
    //   ^^^^
  }
}
Type '{}' is not assignable to type 'T'.
  '{}' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.ts(2322)

Answer №1

Specify that the type for vars is a Partial<T>

class MyClass<T> {
  public vars: Partial<T>;

  constructor() {
    this.vars = {};
  }
}

By doing this, TypeScript understands that all properties are optional.

const x = new MyClass<MyType>();

console.log(x.vars.foo); // displays "undefined" without a TS error
console.log(x.vars.thing); // displays "undefined" with a TS error for unknown property.

Once a type is marked as partial, it remains partial. Trying the following will result in a warning.

const x: Partial<FooBar> = {};
const y: FooBar = x; // TS error, as x is partial.

You can enforce the assignment through casting.

const y: FooBar = x as FooBar;

The concept here is that since the variable is already defined as partial, it's uncertain whether it truly contains values.

You can use a run-time type verifier to validate:

export function isMyType(value: any): value is MyType {
   return typeof value['foot'] === 'string'
          && typeof value['bar'] === 'number';
}

const x: Partial<MyType> = {};

if(isMyType(x)) {
    const y: MyType = x; // no TS error due to the isMyType check
}

I can't recall the term for the is operator in TypeScript, but when utilized within a condition block, it alters the type for the checked variable. Hence, TypeScript doesn't raise an issue.

Furthermore, it provides an opportunity to throw a run-time error if the value isn't as expected.

Answer №2

The mistake follows a clear line of reasoning.

Looking at the first instance of MyType, here is the modified version of MyClass without the generic:

interface MyType {
  foo: string;
  bar: number;
}

class MyClass {
  public vars: MyType;

  constructor() {
    this.vars = {};
  }
}

Because vars now requires both foo and bar properties, the assignment vars = {} is no longer valid.

Answer №3

Perhaps this could be the solution

class NewClass<U extends {[property: string]: any}> {
  public properties: U;

  constructor() {
    this.properties = {};
  }
}

For example:

interface NewType {
  hello: string;
  world: number;
}
let instance = new NewClass<NewType>();

You can access the properties of instance, which will be of type NewType, and see the hello and world properties in instance.properties

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

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 ...

Unlock specific elements within the "sub-category" of a combined collection

If my union type is structured like this: type StateUpdate = { key: 'surname', value: string } | { key : 'age', value: number }; This setup is convenient because it allows me to determine the type of the value based on the key. Howev ...

React Router Issue: Component Not Rendering When <nav> Element Is Incomplete

I am currently experiencing an issue with rendering a component in my React TypeScript application using React Router. The problem arises when trying to navigate to the AddTask component by clicking on a link within a <nav> element. Strangely, the co ...

Ways to access UserProfile in a different Dialogio

For the implementation of a chatbot, I am utilizing Microsoft's Bot Builder framework. However, upon implementing an alternative path to the dialog flow, I noticed that the user's Profile references are getting lost. Here is the code snippet fr ...

Ways to toggle checkboxes to show or hide their child items and subitems

I'm working on creating a straightforward menu that allows users to click on a parent checkbox to expand or collapse its children. The challenge I'm facing is how to make the parent checkboxes expand while hiding the children items within them wh ...

What are the potential downsides of using ID to access HTML elements in React TypeScript?

During my previous job, I was advised against accessing HTML elements directly in React TypeScript using methods like getElementById. Currently, as I work on implementing Chart.js, I initially used a useRef hook for setting up the chart. However, it appear ...

The defineProps<SomePropType>() method is not rendering the props as expected

I have various components, with a parent element where I attempted to pass props using the syntax defineProps<{}>(). The setup is simple, I have a types.ts file at the root level, a parent Vue file (referred to as CardItem), and multiple components. ...

Utilize the identical function within the reducer for numerous mat-slide-toggle and checkboxes in component.html

I'm currently diving into the world of Angular and ngrx while tackling a project focused on enabling users to create forms. In this project, users can add various form elements (such as labels, text fields, dropdown menus, checkboxes, etc.) from a sid ...

Unable to link to 'amount' because it is not a recognized attribute of 'ng-wrapper'

I recently made some changes to my code and now I'm encountering the error message "Can't bind to 'count' since it isn't a known property of 'ng-container'" Instead of having both the notification component and notificat ...

Navigating between pages has become challenging due to issues with the navbar, sidebar,

I successfully developed 4 Angular components: 1st component: menu 2nd component: header 3rd component: home 4th component: login The menu component features a sidebar/navbar created using Material UI. The login component consists of the login page. Howe ...

No matter the circumstances, the "Unexpected end of form" error consistently appears when attempting to upload files in Express

I'm facing a challenge in implementing a file upload API endpoint for my Express+no-stress+Typescript application. Initially, I attempted to use the express-fileupload library, but I quickly realized that it didn't integrate well with Typescript ...

Regular expressions that identify text located at the conclusion of a URL

Although the title may not be entirely appropriate, my goal is to create a regex that will remove any trailing '/' at the end of a URL under certain conditions. For example: http://stackoverflow.com/questions/ask/ to http://stackoverflow.com/qu ...

The action dispatched by "AuthEffects.register$" is not valid and will have no effect

After implementing a new effect for the register action, I encountered the following error message: Effect "AuthEffects.register$" dispatched an invalid action Below is the code for my effect: @Effect() register$ = this.actions$.pipe( ofType<Regis ...

Guide to Dynamically Including an Element in an Array using Typescript

Encountering a type error within the <RenderFormFields formFields={formFieldsData} /> component:- Types of property 'type' are not compatible. Type 'string' cannot be assigned to type '"select"'.ts(2322) Rende ...

A guide on utilizing the TypeScript compilerOptions --outDir feature

Recently, I encountered an error message from the compiler stating: Cannot write file 'path/file.json' because it would overwrite input file. After some investigation, most of the solutions suggested using outDir to resolve this issue. Although t ...

What steps need to be taken in VSCode to import React using IntelliSense?

When I press Enter in that image, nothing seems to occur. I believed IntelliSense would automatically insert import React from 'react'; at the beginning of the file. https://i.stack.imgur.com/7HxAf.png ...

Leveraging Generic Types in React with TypeScript for Dynamically Assigning HTML Props based on Element Tags

I am frequently in need of a component that is essentially just a styled HTML tag. A good example is when I want to create beautifully styled div elements: // Definitions const styledDiv = (className: string) => { const StyledDiv = React.FC<HTMLA ...

Iterating over an object and inserting values into a JavaScript object using the ascending count as the identifier

Illustration: { Are you a coffee drinker?: yes, Do you like to exercise regularly?: no, How often do you eat out at restaurants?: 3 times a week, What is your favorite type of cuisine?: Italian } Results: {yes: 1, no: 1, 3 time ...

Generate a dynamic interface using properties and options provided in a JavaScript object

Can Typescript support the following scenario: I have a structure where keys represent properties and values are arrays of options for those properties: const options = { foo: [fooOption1, fooOption2, ...], bar: [barOption1, barOption2, ...], ... } ...

Hold tight for the function to complete

Here is the structure I am working with: function1 () { A; function2(){}; B; } Is there a way to make function2 return a result before executing B? Currently it is always A->B->function2 Any insights or suggestions would be greatly appreciated. Sin ...