7

Assume that I have an object containing some data. I want to build a generic mapper (only a function respectively - I don't want to instantiate a new class all the time) for all types to use like this: this.responseMapper.map<CommentDTO>(data);

It should simply take all properties from the given type and map the data to it. What I tried so far:

public map<T>(values: any): T {
    const instance = new T();

    return Object.keys(instance).reduce((acc, key) => {
        acc[key] = values[key];
        return acc;
    }, {}) as T;
}

new T(); will throw an error: 'T' only refers to a type, but is being used as a value here.

What's the correct way to do this?

sandrooco
  • 8,016
  • 9
  • 48
  • 86
  • If you add a parameter `type: typeof T` and call `new type()`, does it compile? – cbr Jan 09 '18 at 11:12
  • No, it doesn't and no, this isn't really a duplicate because I don't want to instantiate new mapper classes all the time. – sandrooco Jan 09 '18 at 12:06
  • I see, thanks for the clarification. The phrasing made it look like that was exactly what you were trying to do. If you're trying to just filter out the keys which don't belong in the type's keys, some functional libraries call this operation [`pick`](https://lodash.com/docs/#pick). – cbr Jan 09 '18 at 12:22
  • So would you mind removing the flag? – sandrooco Jan 09 '18 at 15:59
  • I can cast a vote to reopen just as I can cast to close. I'll do that. You can clarify your question by editing it, which will land the question into the reopen queue. – cbr Jan 09 '18 at 17:28

1 Answers1

6

You need to pass the type constructor to the method. Typescript erases generics at runtime to T is not known at runtime. Also I would tighten values a bti to only allow memebers of T to be passed in. This can be done with Partial<T>

public map<T>(values: Partial<T>, ctor: new () => T): T {
    const instance = new ctor();

    return Object.keys(instance).reduce((acc, key) => {
        acc[key] = values[key];
        return acc;
    }, {}) as T;
 }

Usage:

class Data {
    x: number = 0; // If we don't initialize the function will not work as keys will not return x
}

mapper.map({ x: 0 }, Data)
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • That makes sense - if I log the `instance` though, I don't see any properties of it? How does the ` new () => T` syntax work? Any docs for that topic? – sandrooco Jan 09 '18 at 11:53
  • 1
    That all depends on how that class was defined. If you just declare properties you will not see any values at runtime until they are assigned. The `new ()=> T` is a constructor signature, and will execute the constructor of the class. in the example above taht would be equivalent to saying `new Data()`. – Titian Cernicova-Dragomir Jan 09 '18 at 12:08
  • Edited the code to initialize `x` to make the point about having to initialize not just declare the field – Titian Cernicova-Dragomir Jan 09 '18 at 12:09
  • Thanks for the great explanation! – sandrooco Jan 09 '18 at 12:11