Jonathan Tuzman
Jonathan Tuzman

Reputation: 13262

Partial<SomeClass> but without methods?

I'll often make a class with a constructor that can take any arbitrary members that are appropriate to the class:

class SomeClass {
  propA: string;
  propB: number;

  constructor(obj: Partial<SomeClass>) {
    Object.keys(obj).forEach(key => this[key] = obj[key]);
  }
}

So then I can do new SomeObject({propA: 'whatever'}) or new SomeObject({propB: 3}) or any combination (obviously this is more useful when there are more properties.

(Assume this is a low-stakes personal project and we can ignore whatever reasons there might be for not writing a constructor like this.)

So, this is great, and it also means Intellisense will show me the options as I construct a new instance of the class.

However, that Intellisense list also includes the methods of the class (although my example doesn't have any methods, I think you get the idea).

Is there a variant or constraint for Partial<SomeClass> that will only allow properties? And since I often use arrow functions, I guess they would be properties that aren't functions (since I think arrow functions are technically properties not methods).

Upvotes: 4

Views: 517

Answers (1)

Alex Wayne
Alex Wayne

Reputation: 187034

You can use a mapped + conditional type get the keys that are not functions with something like:

type NonMethodKeys<T> = {
    [K in keyof T]: T[K] extends Function ? never : K
}[keyof T]

This goes through all object keys, and if it's a function, it sets the property type to never, else whatever the key K is. Then you index it by [keyof T] which returns all values that are non never. This gives you a union of property names that not functions.

Then you can simply Pick those keys from a type.

type WithoutMethods<T> = Pick<T, NonMethodKeys<T>>

And then use it like so:

type A = WithoutMethods<{ a(): number, b: string}> // type: { b: string }

Or in your case:

constructor(obj: Partial<WithoutMethods<SomeClass>>) { /* ... */ }

Playground


In typescript 4.1 (unreleased, currently in beta), this gets a little easier with Key Remapping in Mapped Types and an as clause of the mapped key. By mapping the key to never, it's omitted.

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

type A = WithoutMethods<{ a(): number, b: string}> // type: { b: string }

Playground

Upvotes: 4

Related Questions