Home > Mobile >  How to get all the properties (but not methods) from a class instance?
How to get all the properties (but not methods) from a class instance?

Time:11-05

I want to create a derived from a class object type, which has all the properties from the class instance. Simply put, I need a type of the result of spreading class instance to a plain object.

Just like this:

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 };

But the example above doesn't work, TS says Property 'bar' is missing in type '{ foo: number; }' but required in type '{ foo: number; bar: () => void; }'.

I'm confused a little, it seems to me as a very simple and regular task. Is there a good and reliable solution?

Thank you.

CodePudding user response:

You need to map over each property separately and do something different conditionally with each of those properties. But your only mapped type:

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

Treats all properties identically.


You can take advantage of key renaming in a mapped type to conditionally map the function properties to never, which removes 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]
}

First, notice I abstracted Newable and AnyFn to their own types to keep this readable. And your use of infer is covered by the built in InstanceType.

Now for the mapped type, it starts my iterating over all the keys of the instances of the class. We use as to change the property name. That new property name is a conditional type that checks to see if the property is a function, which it then maps to never, or other wise leaves the key unchanged.

The value of each mapped property is the same as it would have been on the instance. In other words the value type is unchanged.

This now does what you want:

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

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

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

See playground


However, your type only deals with the instance type, so you can make this much simpler if you pass that in directly:

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 }; // fine

See playground

  • Related