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

Looping through children components in a LitElement template

I aim to generate <slot>s for each child element. For instance, I have a menu and I intend to place each child inside a <div> with a item class. To achieve this, I have devised a small utility function for mapping the children: export functio ...

Issue encountered during mozjpeg installation - unable to locate mozjpeg's cjpeg in the vendor directory due to

During my attempt to set up mozjpeg within a Docker container running NAME="Alpine Linux" ID=alpine VERSION_ID=3.11.7 PRETTY_NAME="Alpine Linux v3.11" HOME_URL="https://alpinelinux.org/" BUG_REPORT_URL="https://bugs.alpin ...

Conceal multiple parameters within routing for added security

I have setup my Angular component with a button that triggers an event. Inside this event, I currently have: this.router.navigate('page2') While I am aware that query parameters can be passed inside the URL, I am faced with the challenge of pas ...

Tips for accessing other environment variables within the environment.ts file in an Angular project

Currently, I am working on modifying the 'environment.ts' file within an Angular project to include additional properties. The current setup looks like this: export const environment = { production: false, apiUrl: 'http://example.com&ap ...

Template URI parameters are being used in a router call

Utilizing the useRouter hook in my current project. Incorporating templated pages throughout the application. Implementing a useEffect hook that responds to changes in the router and makes an API call. Attempting to forward the entire URL to /api/${path ...

Issue: Module '@nrwl/workspace/src/utilities/perf-logging' not found

I attempted to run an Angular project using nxMonorepo and made sure to install all the necessary node modules. However, when I tried running the command "nx migrate --run-migrations" in my directory PS C:\Users\Dell\Desktop\MEANAPP&bso ...

How can I utilize npm with the original source code instead of minified or bundled code?

I am looking to access npm and JavaScript (or TypeScript) 3rd party libraries directly from the source code. Similar to how I can make changes in Python libraries by going into their source code, I want to have the same capability with my JavaScript depen ...

Can we establish communication between the backend and frontend in React JS by utilizing localstorage?

Trying to implement affiliate functionality on my eCommerce platform. The idea is that users who generate links will receive a commission if someone makes a purchase through those links. However, the challenge I'm facing is that I can't store the ...

What is the best way to showcase the outcome of a function on the user interface in Angular 2?

The code snippet below was originally in my .component.html file: <div class="someContainer"> <div class="text--bold">Display this please:</div> <div>{{ myObject.date ? '2 Jun' : 'Now' }}</div&g ...

Mapping a response object to a Type interface with multiple Type Interfaces in Angular 7: A step-by-step guide

Here is the interface structure I am working with: export interface ObjLookup { owner?: IObjOwner; contacts?: IOwnerContacts[]; location?: IOwnerLocation; } This includes the following interfaces as well: export interface IObjOwner { las ...

What is causing my function to execute twice in all of my components?

One issue I am facing is that I have created three different components with routing. However, when I open these components, they seem to loop twice every time. What could be causing this behavior and how can I resolve it? For instance, in one of the comp ...

The ESlint extension in VScode encountered compatibility issues on the MacBook Pro 2021 powered by the Apple M1 Pro chip

After using eslint in vscode on Apple M1 Pro chips, I encountered an issue. I was able to execute eslint via the command line without any problems: > eslint app.js /playground/nodejs/eslint-demo/app.js 1:7 error 'a' is assigned a value ...

Checking for Webpack has begun in a separate process - not found

After working on my Angular2 + Typescript project with Webpack, I noticed a change in the bundling process. Previously, the console output would display three specific comments at the end: webpack: bundle is now VALID [default] Checking started in sepear ...

Is it possible to derive a TypeScript interface from a Mongoose schema without including the 'Document' type?

Using ts-mongoose allows me to define interfaces and schemas for my data in one place. I then export them as a mongoose schema along with the actual interface. The challenge I'm facing is finding a simple way to extract that interface without includi ...

Retrieve the value of the Type Property dynamically during execution

Looking at this type generated from a graphql schema: export type UserPageEntry = { readonly __typename?: 'UserPageEntry' } I am interested in retrieving the string representation of its only property type ('UserPageEntry') during co ...

Troubleshooting problems with contenteditable and input in Firefox and Safari on Angular 5

Currently, I am in the process of creating a table with cells that are editable. However, I am facing a challenge in updating the visual and typescript code simultaneously. I have explored various alternatives but unfortunately, none of them seem to work. ...

A Typescript generic that creates a set of builder function interfaces

Consider a Typescript interface: interface Product { url: URL; available: boolean; price: number; } We need to create a generic type that can produce a builder type based on any given interface: interface ProductSteps { url: (data: unknown) => ...

What is the best way to evaluate typing into an input field?

My objective is to test the 'typing' functionality in an input element. The aim is to insert a value into the input element, verify that its binding successfully captures the value, and observe the entered value within the input element. Below i ...

Expanding the session object with express-session

Seeking assistance with TypeScript and Express session integration. I've been exploring ways to extend my session object, specifically through merging typings based on the documentation provided: In my types/session.d.ts file, I have the following i ...

Converting a nested JSON object into a specific structure using TypeScript

In the process of developing a React app with TypeScript, I encountered a challenging task involving formatting a complex nested JSON object into a specific structure. const data = { "aaa": { "aa": { "xxx&qu ...