What is the best way to access all the attributes (excluding methods) of an object in a class instance?

My goal is to generate a new object type that inherits all the properties from an existing class instance. In other words, I am looking for a way to transform a class instance into a plain object.

For example, consider the following scenario:

class Foobar {
    foo: number = 0;

    bar(): void {} 
}

type ClassProperties<C extends new(...args: readonly unknown[]) => unknown> =
    C extends new(...args: readonly unknown[]) => infer R 
        ? { [K in keyof R]: R[K] } 
        : never
;

const foobar = new Foobar();
const data: ClassProperties<typeof Foobar> = { ...foobar };

However, when I try to implement this, TypeScript throws an error saying

Property 'bar' is missing in type '{ foo: number; }' but required in type '{ foo: number; bar: () => void; }'
.

I find this issue puzzling since it appears to be a straightforward task. Is there a reliable solution to this problem?

Any insights would be highly appreciated.

Answer №1

To achieve the desired result, you must iterate over each property individually and apply different conditions to each one. However, the current mapped type you are using treats all properties identically:

{ [K in keyof R]: R[K] } 

A more effective approach is to utilize key renaming within a mapped type to conditionally map function properties to never, effectively removing them from the resulting type:

type Newable = { new(...args: readonly unknown[]): unknown }
type AnyFn = (...args: unknown[]) => unknown

type ClassProperties<C extends Newable> = {
    [
        K in keyof InstanceType<C>
            as InstanceType<C>[K] extends AnyFn
                ? never
                : K
    ]: InstanceType<C>[K]
}

In this updated version, I have defined separate types for Newable and AnyFn for clarity. Additionally, I replaced your use of infer with TypeScript's built-in InstanceType.

The mapped type now iterates over each key of the class instances. Using as, it applies a conditional check to each property name. If the property is a function, it is mapped to never; otherwise, the key remains unchanged.

The value of each mapped property remains the same as it was on the instance, ensuring the value type is preserved.

This approach accurately produces the expected outcome:

class Foobar {
    foo: number = 0;
    bar(): void {} 
}

type Test = ClassProperties<typeof Foobar>
// { foo: number }

const foobar = new Foobar();
const data: ClassProperties<typeof Foobar> = { ...foobar }; // works correctly

See playground


Alternatively, you can simplify the process by directly passing in the instance type to your type definition:

type ClassProperties<C> = {
    [K in keyof C as C[K] extends AnyFn ? never : K]: C[K]
}

class Foobar {
    foo: number = 0;
    bar(): number {return 123} 
}

type Test = ClassProperties<Foobar>
// { foo: number }

const foobar = new Foobar();
const data: ClassProperties<Foobar> = { ...foobar }; // works fine

See playground

Answer №2

Looking to enhance the previous type mentioned in Alex Wayne's response by utilizing the global type Function. This choice is preferred as it seamlessly integrates with class methods and offers a more concise solution:

type ClassProperties<C> = {
    [K in keyof C as C[K] extends Function ? never : K]: C[K]
}

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

Retrieve the service variable in the routing file

How do I access the service variable in my routing file? I created a UserService with a variable named user and I need to use that variable in my routing file. Here is the approach I tried, but it didn't work: In the routing file, I attempted: cons ...

Exploiting the Power of useRef with TypeScript in Functional Components of React

I'm having trouble accessing the child component method from the parent component using useRef. Eventually, the SayHi method will be responsible for updating the hook state in the child component. Unfortunately, I am encountering some bugs that I can ...

Adding dynamic row values to a bootstrap table in angular 4 - what's the best approach?

When using an Angular 4 bootstrap table, I am encountering an issue where only the first row value is displayed in the table from the backend when entering a product in the text box and pressing enter. Even though the alert window shows the data for the se ...

What is the reason for encountering the error message "Property 'floatThead' does not exist on type 'JQuery<any>' when trying to use floatThead in Angular/TypeScript?

For my project, I am incorporating the third-party jQuery library called https://github.com/mkoryak/floatThead. To work with Bootstrap and jQuery, I have installed them using NPM through the command line. Additionally, I have used NPM to install floatThea ...

typescript: exploring the world of functions, overloads, and generics

One interesting feature of Typescript is function overloading, and it's possible to create a constant function with multiple overloads like this: interface FetchOverload { (action: string, method: 'post' | 'get'): object; (acti ...

Achieve the capability to upload multiple files in Next.js using the upload.io integration feature

I'm currently using upload.io for uploads and replicate.com for an AI model on a specific app. I am able to upload one picture, but unfortunately, I am encountering issues when trying to upload multiple pictures. Can anyone identify the problem here? ...

Issue encountered while setting up controls and AbstractControls in form development

Here is a snippet of code showing how I create and manipulate a form in Angular: this.myForm = new FormGroup({ points: new FormArray([ new FormGroup({ date: this.date, startTime: new FormControl(null, Val ...

Typescript is throwing an error with code TS2571, indicating that the object is of type 'unknown'

Hey there, I'm reaching out for assistance in resolving a specific error that has cropped up. try{ } catch { let errMsg; if (error.code === 11000) { errMsg = Object.keys(error.keyValue)[0] + "Already exists"; } return res.status ...

"Attempting to verify a JSON Web Token using a promise that returns an object not compatible with the specified

Learning about Typescript has been quite a challenge for me, especially when it comes to using the correct syntax. I have implemented a promise to retrieve decoded content from jwt.verify - jsonwebtoken. It is functioning as intended and providing an obje ...

The specified type 'MutableRefObject<HTMLInputElement | undefined>' cannot be assigned to type 'LegacyRef<HTMLInputElement> | undefined'

Consider the following simplified component : const InputElement => React.forwardRef((props:any, ref) => { const handleRef = React.useRef<HTMLInputElement|undefined>() React.useImperativeHandle(ref, () => ({ setChecked(checke ...

Experiencing CORS problem in Ionic 3 when accessing API on device

I am a newcomer to IONIC and I am utilizing a slim REST API with Ionic 3. Currently, I am encountering the following error: "Failed to load : Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin&apos ...

Tips for managing server data and dynamically binding values in Ionic 3

I am struggling with handling data retrieved from the server. I have a provider that fetches the data through HTTP, and I want to ensure the data is loaded before the page loads. However, there is a delay in reflecting the data on the page. Can someone pro ...

Error in Mongoose Schema Configuration Detected in NestJS App

I'm currently developing an e-commerce application using NestJS and MongoDB with Mongoose. I've been facing an issue while trying to implement a user's shopping cart in the application. The error message I keep encountering is as follows: ...

Injecting singletons in a circular manner with Inversify

Is it possible to use two singletons and enable them to call each other in the following manner? import 'reflect-metadata'; import { Container, inject, injectable } from 'inversify'; let container = new Container(); @injectable() cla ...

Using Typescript to combine strings with the newline character

Currently, I am delving into Angular2 and facing the challenge of creating a new line for my dynamically generated string. For example: input: Hello how are you ? output: Hello how are you? Below is the code snippet: .html <div class="row"> ...

Angular project hosting causing malfunctions in route parameters

Encountering a problem with route parameters after deploying my website on namecheap hosting. Routes Setup: const routes: Routes = [ { path: 'women', component: ProductlistingComponent }, { path: 'women/:search_1', component: ...

Excessive repetition in the style of writing for a function

When it comes to TypeScript, a basic example of a function looks like this: let myAdd: (x: number, y: number) => number = function ( x: number, y: number ): number { return x + y; }; Why is there redundancy in this code? I'm having trouble g ...

When trying to reference a vanilla JavaScript file in TypeScript, encountering the issue of the file not being recognized

I have been attempting to import a file into TypeScript that resembles a typical js file intended for use in a script tag. Despite my efforts, I have not found success with various methods. // global.d.ts declare module 'myfile.js' Within the re ...

Utilize API within an array to enable Ionic to display a PDF in a document viewer

Recently diving into the world of Angular and Ionic, I've come across some interesting API data: [{"ID":"1","Title":"Maritime Safety","File_Name":"9c714531945ee24345f60e2105776e23.pdf","Created":"2018-11-07 17:36:55","Modified":"2018-11-07 17:36:55"} ...

An issue arises in Typescript when attempting to pass an extra prop through the server action function in the useForm

I am struggling with integrating Next.js server actions with useFormState (to display input errors on the client side) and Typescript. As per their official documentation here, they recommend adding a new prop to the server action function like this: expo ...