6

Is there a good way to map objects to other objects? Library recommendations welcome too.

For example, say I have these classes:

export class Draft {
    id: number;
    name: string;
    summary: string;
}

export class Book {
    id: number;
    name: string;
    info: Info;
}

export class Info {
    value: string;
}

Traditionally, to map fields from Draft to Book I'd have to do so manually for each field:

export class Book {
    [...]

    fromDraft(Draft draft) {
        this.id = draft.id;
        this.name = draft.name;
        this.info = new Info();
        this.info.value = draft.summary;
    }
}

Is there an easier way to map object fields that are named the same way? For example Draft and Book's id and name fields, while also being able to define custom mappings, like from draft.summary to book.info.value.

Note that this is far from my actual use case. Here it's not so bad to do assignments manually, but for objects with many similarly named fields it's quite a chore.

Thanks!

Daniel
  • 3,541
  • 3
  • 33
  • 46
mintychai
  • 281
  • 3
  • 10
  • Really not sure what your question mean, but can you not simply abstract both `Book` and `Draft` into a more generic parent class that shares the common fields, and `extend` from that parent class? Alternatively, you can use [decorators](https://tc39.github.io/proposal-decorators/). – d4nyll Jul 17 '17 at 20:40
  • That could be an option, thanks! – mintychai Jul 17 '17 at 21:00

2 Answers2

3

With regards to copying properties of the same name, you can use Object.assign:

fromDraft(Draft draft) {
    Object.assign(this, draft);
    this.info = new Info();
    this.info.value = draft.summary;
}

The problem is that it will also create this.summary, but it's a convenient way of copying properties so if you can model your classes differently then you can use it.

Another option is to have a list of shared property names and then iterate it:

const SHARED_NAMES = ["id", "name"];

fromDraft(Draft draft) {
    SHARED_NAMES.forEach(name => this[name] = draft[name]);
    this.info = new Info();
    this.info.value = draft.summary;
}
Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
2

Regardless of TypeScript, you can use lodash _.mergeWith and pass your merge function.

Advantage: More generic (If you have more logic you can add it (for complex types for example)

Disadvantage: You need lodash

Something like:

var a = {
  foo: [1, 2, 3],
  bar: true
}

var b = {
  foo: [4, 5, 6, 7],
  bar: false
}

var c = _.mergeWith(a, b, function(a, b) {
  if (a.concat) {
    return a.concat(b);
  }
  else {
    return b;
  }
})

console.log('c', c);
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>

http://jsbin.com/xugujon/edit?html,js

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135