Clean way to map objects to other objects?

2020-04-08 12:51发布

问题:

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!

回答1:

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



回答2:

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