web-dev-qa-db-ja.com

エンティティをDTOにキャスト

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

}

ClassSerializerInterceptorが写真オブジェクトをDTOにシリアル化し、次のようなものを返すことを期待していました。

{
  id: 1,
  name: 'Photo 1'
}

しかし、私はまだすべてのプロパティを含む応答を取得しています:

{
  id = 1,
  name = 'Photo 1',
  description = 'Photo 1 description',
  filename = 'file.png',
  views = 10,
  isPublished = true
}

基本的に、DTOで定義されていないすべてのプロパティを削除します。

@Exclude()を使用するとClassSerializerInterceptorが完全に機能することを知っています。未定義のプロパティも削除されることを期待していました。

これについて最善の方法はありますか?私は次のようなことができることを知っています:

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

しかし、応答に別のプロパティを追加したい場合は、クラスに新しいプロパティを追加するだけでは不十分です。それを行うためのより良い「ネスト方法」があるかどうか疑問に思っています。

9
Lewsmith

可能なオプションの1つは、DTOオブジェクトを_@Exclude_および_@Expose_デコレーターでマークしてから、plainToClassで変換を行うことです。

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

   @Expose()
   @IsString()
   readonly name: string;
}
_

上記のように装飾したとすると、次のことができます:const dto = plainToClass(PhotoSnippetDto, photo);

結果のオブジェクトは、idnameのみが最終的なオブジェクトに表示される、期待どおりの形式です。後でさらにプロパティを公開することにした場合は、それらをDTOに追加して、_@Expose_でタグを付けることができます。

このアプローチでは、_Object.assign_を使用しているDTOからコンストラクターを削除することもできます。

9
Jesse Carter

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

次に、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)));
    }
}

次に、このインターセプターを使用して、データを任意のタイプに変換します。

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

これは私が欲しかったものを私に与えます:

{
  id: 1,
  name: 'Photo 1'
}

間違いなく巣のように感じます!必要な場所で同じインターセプターを使用でき、DTOを変更するだけで応答を変更できます。

幸せな日々。

9
Lewsmith

すべての型変換について、オブジェクトの変換を容易にするために、ライブラリ metamorphosis-nestjs を開発しました。
変換サービスに早期に登録された、コンバーターによって提供されたすべての変換のための注入可能な変換サービスの欠けている概念をNestJSに追加します(Javaアプリ)。

だからあなたの場合:

  1. npm install --save @fabio.formosa/metamorphosis-nest
    
  2. import { MetamorphosisNestModule } from '@fabio.formosa/metamorphosis-nest';
    
    @Module({
      imports: [MetamorphosisModule.register()],
      ...
    }
    export class MyApp{ }
    
  3. import { Convert, Converter } from '@fabio.formosa/metamorphosis';
    
    @Injectable()
    @Convert(Photo, PhotoSnippetDto )
    export default class PhotoToPhotoSnippetDtoConverter implements Converter<Photo, 
    PhotoSnippetDto> {
    
    public convert(source: Photo): PhotoSnippetDto {
      const target = new PhotoSnippetDto();
      target.id = source.id;
      target.name = source.name;
      return target;
     }
    }
    

最後に、conversionServiceを注入して使用することができます。

 const photoSnippetDto = <PhotoSnippetDto> await this.convertionService.convert(photo, PhotoSnippetDto);

この質問の他の回答に関連して、あなたはあなたの変換メソッドplainToClassに使うことができます:

 @Injectable()
 @Convert(Photo, PhotoSnippetDto )
 export default class PhotoToPhotoSnippetDtoConverter implements Converter<Photo, PhotoSnippetDto> {

   public convert(source: Photo): PhotoSnippetDto {
     return plainToClass(PhotoSnippetDto, source);
   }
 }

必要に応じて、インターセプター内でもconversionServiceを呼び出すことができます。

READMEを取得して、 metamorphosis-nestjs の例とすべての利点を確認してください。

0
Fabio Formosa