将实体转换为dto

Lew*_*ith 8 node.js typescript nestjs

只是想知道将NestJS实体对象转换为DTO的最佳方法.

可以说我有以下内容:

import { IsString, IsNumber, IsBoolean } from 'class-validator';
import { Exclude } from 'class-transformer';

export class PhotoSnippetDto {
  @IsNumber()
  readonly id: number;

  @IsString()
  readonly name: string;

  constructor(props) {
    Object.assign(this, props);
  }
}

export class Photo {

  @IsNumber()
  id: number;

  @IsString()
  name: string;

  @IsString()
  description: string;

  @IsString()
  filename: string;

  @IsNumber()
  views: number;

  @IsBoolean()
  isPublished: boolean;

  @Exclude()
  @IsString()
  excludedPropery: string;

  constructor(props) {
    Object.assign(this, props);
  }
}

@Controller()
export class AppController {

  @Get()
  @UseInterceptors(ClassSerializerInterceptor)
  root(): PhotoSnippetDto {
    const photo = new Photo({
      id: 1,
      name: 'Photo 1',
      description: 'Photo 1 description',
      filename: 'photo.png',
      views: 10,
      isPublished: true,
      excludedPropery: 'Im excluded'
    });

    return new PhotoSnippetDto(photo);
  }

}
Run Code Online (Sandbox Code Playgroud)

我期待ClassSerializerInterceptor将照片对象序列化到DTO并返回如下内容:

{
  id: 1,
  name: 'Photo 1'
}
Run Code Online (Sandbox Code Playgroud)

但是我得到了一个包含所有属性的响应:

{
  id = 1,
  name = 'Photo 1',
  description = 'Photo 1 description',
  filename = 'file.png',
  views = 10,
  isPublished = true
}
Run Code Online (Sandbox Code Playgroud)

我基本上想要去除DTO中未定义的所有属性.

我知道ClassSerializerInterceptor在使用@Exclude()时效果很好,我还希望它也能删除未定义的属性.

我很好奇这个最好的方法吗?我知道我可以这样做:

@Get('test')
@UseInterceptors(ClassSerializerInterceptor)
test(): PhotoSnippetDto {
  const photo = new Photo({
    id: 1,
    name: 'Photo 1',
    description: 'Photo 1 description',
    filename: 'photo.png',
    views: 10,
    isPublished: true,
    excludedPropery: 'Im excluded'
  });
  const { id, name } = photo;
  return new PhotoSnippetDto({id, name});
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我想要在响应中添加另一个属性,我必须做的不仅仅是将新属性添加到类中.我想知道是否有更好的"Nest方式".

Lew*_*ith 9

因此,基于Jesse的精彩答案,我最终使用@Exclude()和@Expose()创建了DTO,以删除所有暴露的属性:

import { IsString, IsEmail } from 'class-validator';
import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class PhotoSnippetDto {
   @Expose()
   @IsNumber()
   readonly id: number;

   @Expose()
   @IsString()
   readonly name: string;
}
Run Code Online (Sandbox Code Playgroud)

然后我创建了一个泛型变换拦截器,它调用plainToclass来转换对象:

import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { plainToClass } from 'class-transformer';

interface ClassType<T> {
    new(): T;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<Partial<T>, T> {

    constructor(private readonly classType: ClassType<T>) {}

    intercept(context: ExecutionContext, call$: Observable<Partial<T>>, ): Observable<T> {
        return call$.pipe(map(data => plainToClass(this.classType, data)));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用此拦截器将数据转换为任何类型:

@Get('test')
@UseInterceptors(new TransformInterceptor(PhotoSnippetDto))
test(): PhotoSnippetDto {
  const photo = new Photo({
    id: 1,
    name: 'Photo 1',
    description: 'Photo 1 description',
    filename: 'photo.png',
    views: 10,
    isPublished: true,
    excludedPropery: 'Im excluded'
  });
  return photo;
}
Run Code Online (Sandbox Code Playgroud)

这给了我想要的东西:

{
  id: 1,
  name: 'Photo 1'
}
Run Code Online (Sandbox Code Playgroud)

绝对感觉更像鸟巢!我可以在任何需要的地方使用相同的拦截器,并改变我只需要更改DTO的响应.

快乐的时光.

  • 嘿,我尝试了您的解决方案,但收到运行时错误:“错误:Nest 无法解析 TransformInterceptor 的依赖项(?)”。请确保索引 [0] 处的参数对象在****模块上下文中可用。` (2认同)

Jes*_*ter 8

一种可能的选择是使用@Exclude@Expose装饰器标记DTO对象,然后使用以下命令进行转换plainToClass:

@Exclude()
export class PhotoSnippetDto {
   @Expose()
   @IsNumber()
   readonly id: number;

   @Expose()
   @IsString()
   readonly name: string;
}
Run Code Online (Sandbox Code Playgroud)

假设你已按照上面的装饰,那么你可以这样做: const dto = plainToClass(PhotoSnippetDto, photo);

生成的对象采用您期望的形式,idname显示在最终对象上.如果您稍后决定公开更多属性,只需将它们添加到DTO并用它们标记即可@Expose.

此方法还允许您从正在使用的DTO中删除构造函数 Object.assign

  • @Lewsmith 没问题!很高兴我能帮忙。非常感谢您分享拦截器的完整实现,这看起来是一个很好的解决方案!甚至可能会为未来的 Nest 应用窃取它:P (2认同)