2

assume I have this example class, but in reality it has many more properties

class Foo {
  name: string
  dob: number

  constructor(name: string, dob: number) {
    this.name = name;
    this.dob = dob;
  }

  get age() {
     return new Date().getTime() - this.dob
  }
}

Now Typescript is smart and when I instantiate the class it will give me all the right properties:

var classInstance = new Foo('name', new Date().getTime())

classInstance.name // ok
classInstance.dob // ok
classInstance.age // ok

Somewhere in my code the class gets cloned using the Spread Operator, I'm not sure what TS does behind the scene but it is really smart and gives me all the right properties

var classJSON = {...classInstance};

classJSON.name // ok
classJSON.dob // ok
classJSON.age // missing

tsplayground

This is great, however I sometime need to use the type of classJSON .. The only way I can think to extract it is to do this:

var classJSON  = {...new Foo('', 0)}
type ClassJSONType = typeof classJSON; 

Is there a way to extract the type straight out of Foo without needing Javascript to instantiate?

lonewarrior556
  • 3,917
  • 2
  • 26
  • 55

3 Answers3

3

Disclaimer: this post is a mod based on Matt McCutchen's answer. It's a solid solution, pure TS without any JS runtime effect.

type IfEquals<X, Y, T> =
    (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? T : never;

type JSONify<T> = Pick<T, {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T]>;

Above trick excludes all readonly fields. To further exclude methods, use the following:

type JSONify<T> = Pick<T, {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] extends Function ? never : T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T]>;

Playground

hackape
  • 18,643
  • 2
  • 29
  • 57
  • The question probably should be closed as duplicated, but the bounty prevent the action, so I post this one. – hackape Mar 27 '20 at 04:01
  • Please be aware that this solution cannot exclude inherited methods and excludes `readonly` fields – Shlang Mar 27 '20 at 23:01
  • @Shlang Original answer does excludes `readonly` fields, as to methods, see my edit. – hackape Mar 31 '20 at 06:47
  • Thank you, but it still has some limitations. Please check this [playground](https://stackblitz.com/edit/typescript-nw5qyh) – Shlang Mar 31 '20 at 08:26
  • @Shlang Now I get your point. Yeah you're right about this solution is not covering every aspect. The type system is simply blind to object's own property. – hackape Mar 31 '20 at 08:37
  • While this is good, it does not work when there is something readonly in the constructor which is assignable on creation. So getters and readonly properties are treated the same. – Dmitriy Mar 07 '23 at 04:26
3

If all you're trying to avoid is initialising Foo, you can do something like this:

var funRetClassJSON = () => ({ ...Foo.prototype });
type TypeOfClassJSON = ReturnType<typeof funRetClassJSON>;

or

var nullClassJSON = false as any ? null : ({ ...Foo.prototype });
type AltTypeOfClassJSON = NonNullable<typeof nullClassJSON>;

Of course, both these examples still incur some runtime penalty, as some JS is still generated.

Tiberiu Maran
  • 1,983
  • 16
  • 23
2

It is not possible at the moment because TS type system does not allow tracking non-own properties. There is a proposal for exactly what you want. This comment also describes possible ways to express Spread operator without stripping non-own properties.

Shlang
  • 2,495
  • 16
  • 24