Node.js 學習筆記 -TypeScript AutoMapper 介紹 (一)

Tom
appxtech

--

這是我在Appx 時賦科技實習時接觸專案中使用到的套件,寫這篇的目的是訓練自己閱讀官方文件的能力並且整理成易懂的筆記。 預計會分成2篇撰寫,從基本的使用介紹到更細的 mapping configuration 介紹。

What is AutoMapper

“AutoMapper is an object-object mapper “,也就是能夠幫你將兩個不同型別的物件做快速的轉換,只要透過事先設定好的映射方式,就能很方便的使用並簡化程式碼。

Why use AutoMapper

當你的專案中,有大量且重複的類型轉換邏輯需求,換句話說,若有兩Class ,“userDto” 與 “userEntity" 之間常常需要單向或雙向的 map,且單一方向的map 方式皆固定,則 AutoMapper 就是一個合適的工具選擇。

反之,若你的需求中,userDto 與 userEntity 之間的 map 常因功能不同需要有不同的 map 邏輯,則你需要多拆出幾種對應的 class 來滿足所有 map 的需求,否則AutoMapper 就不適合你的專案。

According to the author of .Net AutoMapper , Jimmy Bogard

AutoMapper works because it enforces a convention. It assumes that your destination types are a subset of the source type. It assumes that everything on your destination type is meant to be mapped. It assumes that the destination member names follow the exact name of the source type. It assumes that you want to flatten complex models into simple ones.

All of these assumptions come from our original use case — view models for MVC, where all of those assumptions are in line with our view model design. With AutoMapper, we could enforce our view model design philosophy. This is the true power of conventions — laying down a set of enforceable design rules that help you streamline development along the way.

How to use AutoMapper

第一步 : 安裝 AutoMapper
classes:
npm i @automapper/core @automapper/classes reflect-metadata #classes
pojos:
npm i @automapper/core @automapper/pojos #pojos

這邊可以依照開發方法,選擇 classes 或 pojos 其中一種 strategy 來安裝,
本篇將使用 classes ,pojos 的用法可以參考 這裡

第二步 : 宣告 class

此處 “@AutoMap” 的 decorator 是用來定義哪些資料會被 map,並且在括號內宣告該變數的 metadata給 AutoMpper,使 AutoMapper 在 Map 時能判斷正確型別。

宣告完成後 AutoMapper 會自動去尋找變數名稱相同的資料做Map

* ./entity/UserInfo.entity.tsimport { AutoMap } from '@automapper/classes';
export class UserInfo {
@AutoMap(() => String)
userName: string;
@AutoMap(() => String)
userId: string;
@AutoMap(() => String)
phoneNumber: string;
@AutoMap(() => Date)
birthDay: Date = new Date();
@AutoMap(() => Address)
address: Address;
@AutoMap(() => Date)
registrationDate: Date = new Date();
//password 不需 map 所以不添加 @AutoMap
password: string;
}
export class Address {
@AutoMap(() => String)
city: string;
@AutoMap(() => String)
street: string;
@AutoMap(() => String)
number: string;
}
* ./dto/UserInfo.dto.tsimport { AutoMap } from '@automapper/classes';

export class UserInfoDto {
@AutoMap(() => String)
userName: string;
@AutoMap(() => String)
userId: string;
@AutoMap(() => String)
phoneNumber: string;
@AutoMap(() => String)
birthDay: string;
@AutoMap(() => AddressDto)
address: AddressDto;
@AutoMap(() => String)
registrationDate: string;
}
export class AddressDto {
@AutoMap(() => String)
city: string;
@AutoMap(() => String)
street: string;
@AutoMap(() => String)
number: string;
}

第三步 : 設定 AutoMapper

*./configs/mapperConfigs.tsimport {createMapper,createMap,typeConverter}from‘@automapper/core’;
import { classes } from ‘@automapper/classes’;
import { Address, UserInfo } from ‘../entity/UserInfo.entity’;
import { AddressDto, UserInfoDto } from ‘../dto/UserInfo.dto’;
import moment from ‘moment’;
//宣告 mapper 採用的 strategy
export const mapper = createMapper({ strategyInitializer: classes() });
//建立 Address -> AddressDto 的 map關係
createMap(mapper, Address, AddressDto);
//建立 UserInfo -> UserInfoDto 的 map關係
createMap(
mapper,
UserInfo,
UserInfoDto,
// typeConverter : 只要 source 對應到 destination 的資料中有 Date
// -> String 的關係,就使用後面定義的 function 做轉換
typeConverter(Date, String, (date) => moment(date).format(‘YYYY-
MM-DD’))
);

第四步 : 執行 (測試使用 ts-node 執行)

* main.tsimport { mapper } from './config/MapperConfig';
import { UserInfoDto } from './dto/UserInfoDto';
import { UserInfo } from './entity/UserInfo';
// 建立 UserInfo
const user: UserInfo = new UserInfo();
user.userName = 'Jack';
user.userId = '123456';
user.phoneNumber = '0912345678';
user.birthDay = new Date('1999-01-01 10:12:12');
user.address = { city: 'Taipei', street: 'First', number: '1' };
user.registrationDate = new Date('2020-01-01 10:12:12');
user.password = '345678';
const userInfoDto = mapper.map(user, UserInfo, UserInfoDto);
// 使用 automapper 進行型態轉換
const userInfoDto : UserInfoDto = mapper.map(dto, UserInfo, UserInfoDto);
console.log(userInfoDto);
output : 
------------------------------
UserInfoDto {
registrationDate: '2020-01-01',
address: AddressDto { number: '1',street: 'First',city: 'Taipei'},
birthDay: '1999-01-01',
phoneNumber: '0912345678',
userId: '123456',
userName: 'Jack'
}

Summary

AutoMapper 出現是為了MVC 架構中 view-models 和 entity-models 之間的轉換,因為對應型別的 map 方式較為單純且固定,所以 AutoMapper 能很好的解決問題。

並且 AutoMapper 需要事先設定才能正確地 map,若有複雜的 map 需求則需要複雜的設定,但如果有大量的 map 都需要複雜的設定,則可能要回頭考慮資料結構是不是適當,或許有更好的結構能省下設定的時間。

上述是最基礎的 AutoMapper 使用,AutoMapper 還提供了許多 mapping configuration 可以設定,例如 forMember() 可對特定欄位做處理後再 map 的、 namingConventions() 可依據名稱解析複合資料結構並 map 出資料的 等等,會在下一篇再做說明。

Reference

--

--

Tom
appxtech
Writer for

Major in Computer Science, Web Backend Engineer