TypeScript/Angular2 中的 DTO 设计
DTO Design in TypeScript/Angular2
我目前正在开发 Angular 2 应用程序。在开发过程中,我开始使用 TypeScript classes 从 JSON 创建对象,我通过 HTTP 接收或在表单中创建新对象时。
class 可能看起来像这样。
export class Product {
public id: number;
public name: string;
public description: string;
public price: number;
private _imageId: number;
private _imageUrl: string;
constructor(obj: Object = {}) {
Object.assign(this, obj);
}
get imageId(): number {
return this._imageId;
}
set imageId(id: number) {
this._imageId = id;
this._imageUrl = `//www.example.org/images/${id}`;
}
get imageUrl(): string {
return this._imageUrl;
}
public getDTO() {
return {
name: this.name,
description: this.description,
imageId: this.imageId,
price: this.price
}
}
}
到目前为止,上面显示的这个解决方案效果很好。但是现在让我们假设对象中有更多的属性,我想要一个干净的 DTO(例如没有私有属性)来通过 POST 将这个对象发送到我的服务器。更通用的 getDTO()
函数会是什么样子?我想避免有一长串 属性 赋值。我正在考虑为属性使用装饰器。但我真的不知道如何使用它们来过滤 DTO 的属性。
您可以为此使用 property decorator:
const DOT_INCLUDES = {};
function DtoInclude(proto, name) {
const key = proto.constructor.name;
if (DOT_INCLUDES[key]) {
DOT_INCLUDES[key].push(name);
} else {
DOT_INCLUDES[key] = [name];
}
}
class A {
@DtoInclude
public x: number;
public y: number;
@DtoInclude
private str: string;
constructor(x: number, y: number, str: string) {
this.x = x;
this.y = y;
this.str = str;
}
toDTO(): any {
const includes: string[] = DOT_INCLUDES[(this.constructor as any).name];
const dto = {};
for (let key in this) {
if (includes.indexOf(key) >= 0) {
dto[key] = this[key];
}
}
return dto;
}
}
let a = new A(1, 2, "string");
console.log(a.toDTO()); // Object {x: 1, str: "string"}
如果你愿意,你可以使用他们示例中使用的 the reflect-metadata,我用 DOT_INCLUDES
注册表实现了它,这样它就可以在 playground 中很好地工作,而不需要额外的依赖。
编辑
正如@Bergi 评论的那样,您可以遍历 includes
而不是 this
:
toDTO(): any {
const includes: string[] = DOT_INCLUDES[(this.constructor as any).name];
const dto = {};
for (let ket of includes) {
dto[key] = this[key];
}
return dto;
}
哪个确实更有效率也更有意义。
我使用 class-transformer
进行 DTO 设计。它完成所有肮脏的工作并提供 @Expose()
、@Exclude()
、@Transform()
、@Type()
以及其他几个有用的 属性 注释。只需阅读文档。
这是一个例子:
- 基本 DTO 自动处理序列化和反序列化。
@Transform
转换 SQL 日期 to/from 字符串。您可以使用自己的自定义转换器。
import {
classToPlain,
plainToClass,
Transform,
TransformationType,
TransformFnParams
} from 'class-transformer';
import { DateTime } from 'luxon';
/**
* Base DTO class.
*/
export class Dto {
constructor(data?: Partial<Dto>) {
if (data) {
Object.assign(this, data);
}
}
/**
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#tojson_behavior
*/
toJSON(): Record<string, any> {
return classToPlain(this);
}
static fromJSON<T extends typeof Dto>(this: T, json: string): InstanceType<T> {
return this.fromPlain(JSON.parse(json));
}
/**
* @see https://github.com/Microsoft/TypeScript/issues/5863#issuecomment-528305043
*/
static fromPlain<T extends typeof Dto>(this: T, plain: Object): InstanceType<T> {
return plainToClass(this, plain) as InstanceType<T>;
}
}
/**
* SQL date transformer for JSON serialization.
*/
export function sqlDateTransformer({type, value}: TransformFnParams): Date | string {
if (!value) {
return value;
}
switch (type) {
case TransformationType.PLAIN_TO_CLASS:
return DateTime.fromSQL(value as string).toJSDate();
case TransformationType.CLASS_TO_PLAIN:
return DateTime.fromJSDate(value as Date).toFormat('yyyy-MM-dd HH:mm:ss');
default:
return value;
}
}
/**
* Example DTO.
*/
export class SomethingDto extends Dto {
id?: string;
name?: string;
/**
* Date is serialized into SQL format.
*/
@Transform(sqlDateTransformer)
date?: Date;
constructor(data?: Partial<SomethingDto>) {
super(data);
}
}
// Create new DTO
const somethingDto = new SomethingDto({
id: '1a8b5b9a-4681-4868-bde5-95f023ba1706',
name: 'It is a thing',
date: new Date()
});
// Convert to JSON
const jsonString = JSON.stringify(somethingDto);
console.log('JSON string:', jsonString);
// Parse from JSON
const parsed = SomethingDto.fromJSON(jsonString);
console.log('Parsed:', parsed);
我目前正在开发 Angular 2 应用程序。在开发过程中,我开始使用 TypeScript classes 从 JSON 创建对象,我通过 HTTP 接收或在表单中创建新对象时。
class 可能看起来像这样。
export class Product {
public id: number;
public name: string;
public description: string;
public price: number;
private _imageId: number;
private _imageUrl: string;
constructor(obj: Object = {}) {
Object.assign(this, obj);
}
get imageId(): number {
return this._imageId;
}
set imageId(id: number) {
this._imageId = id;
this._imageUrl = `//www.example.org/images/${id}`;
}
get imageUrl(): string {
return this._imageUrl;
}
public getDTO() {
return {
name: this.name,
description: this.description,
imageId: this.imageId,
price: this.price
}
}
}
到目前为止,上面显示的这个解决方案效果很好。但是现在让我们假设对象中有更多的属性,我想要一个干净的 DTO(例如没有私有属性)来通过 POST 将这个对象发送到我的服务器。更通用的 getDTO()
函数会是什么样子?我想避免有一长串 属性 赋值。我正在考虑为属性使用装饰器。但我真的不知道如何使用它们来过滤 DTO 的属性。
您可以为此使用 property decorator:
const DOT_INCLUDES = {};
function DtoInclude(proto, name) {
const key = proto.constructor.name;
if (DOT_INCLUDES[key]) {
DOT_INCLUDES[key].push(name);
} else {
DOT_INCLUDES[key] = [name];
}
}
class A {
@DtoInclude
public x: number;
public y: number;
@DtoInclude
private str: string;
constructor(x: number, y: number, str: string) {
this.x = x;
this.y = y;
this.str = str;
}
toDTO(): any {
const includes: string[] = DOT_INCLUDES[(this.constructor as any).name];
const dto = {};
for (let key in this) {
if (includes.indexOf(key) >= 0) {
dto[key] = this[key];
}
}
return dto;
}
}
let a = new A(1, 2, "string");
console.log(a.toDTO()); // Object {x: 1, str: "string"}
如果你愿意,你可以使用他们示例中使用的 the reflect-metadata,我用 DOT_INCLUDES
注册表实现了它,这样它就可以在 playground 中很好地工作,而不需要额外的依赖。
编辑
正如@Bergi 评论的那样,您可以遍历 includes
而不是 this
:
toDTO(): any {
const includes: string[] = DOT_INCLUDES[(this.constructor as any).name];
const dto = {};
for (let ket of includes) {
dto[key] = this[key];
}
return dto;
}
哪个确实更有效率也更有意义。
我使用 class-transformer
进行 DTO 设计。它完成所有肮脏的工作并提供 @Expose()
、@Exclude()
、@Transform()
、@Type()
以及其他几个有用的 属性 注释。只需阅读文档。
这是一个例子:
- 基本 DTO 自动处理序列化和反序列化。
@Transform
转换 SQL 日期 to/from 字符串。您可以使用自己的自定义转换器。
import {
classToPlain,
plainToClass,
Transform,
TransformationType,
TransformFnParams
} from 'class-transformer';
import { DateTime } from 'luxon';
/**
* Base DTO class.
*/
export class Dto {
constructor(data?: Partial<Dto>) {
if (data) {
Object.assign(this, data);
}
}
/**
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#tojson_behavior
*/
toJSON(): Record<string, any> {
return classToPlain(this);
}
static fromJSON<T extends typeof Dto>(this: T, json: string): InstanceType<T> {
return this.fromPlain(JSON.parse(json));
}
/**
* @see https://github.com/Microsoft/TypeScript/issues/5863#issuecomment-528305043
*/
static fromPlain<T extends typeof Dto>(this: T, plain: Object): InstanceType<T> {
return plainToClass(this, plain) as InstanceType<T>;
}
}
/**
* SQL date transformer for JSON serialization.
*/
export function sqlDateTransformer({type, value}: TransformFnParams): Date | string {
if (!value) {
return value;
}
switch (type) {
case TransformationType.PLAIN_TO_CLASS:
return DateTime.fromSQL(value as string).toJSDate();
case TransformationType.CLASS_TO_PLAIN:
return DateTime.fromJSDate(value as Date).toFormat('yyyy-MM-dd HH:mm:ss');
default:
return value;
}
}
/**
* Example DTO.
*/
export class SomethingDto extends Dto {
id?: string;
name?: string;
/**
* Date is serialized into SQL format.
*/
@Transform(sqlDateTransformer)
date?: Date;
constructor(data?: Partial<SomethingDto>) {
super(data);
}
}
// Create new DTO
const somethingDto = new SomethingDto({
id: '1a8b5b9a-4681-4868-bde5-95f023ba1706',
name: 'It is a thing',
date: new Date()
});
// Convert to JSON
const jsonString = JSON.stringify(somethingDto);
console.log('JSON string:', jsonString);
// Parse from JSON
const parsed = SomethingDto.fromJSON(jsonString);
console.log('Parsed:', parsed);