node.js - Kast enhet til dto


Bare lurer på den beste måten å konvertere et NestJS-entitetsobjekt til en DTO.

La oss si jeg har følgende:

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

}

Jeg forventet at ClassSerializerInterceptor skulle serialisere fotoobjektet til DTO-en og returnere noe som dette:

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

Men jeg får fortsatt et svar som inneholder alle egenskapene:

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

Jeg vil egentlig fjerne alle egenskaper som ikke er definert i DTO-en.

Jeg vet at ClassSerializerInterceptor fungerer perfekt når man bruker @Exclude(), jeg forventet bare også at det skulle fjerne udefinerte egenskaper.

Jeg er nysgjerrig på hva som er den beste måten å gå frem på? Jeg vet at jeg kunne gjort noe som:

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

Men hvis jeg noen gang vil legge til en ny egenskap til responsen, må jeg gjøre mer enn bare å legge til den nye egenskapen i klassen. Jeg lurer på om det finnes en bedre 'Nest-måte' å gjøre det på.

Svar #1

Så basert på Jesses fantastiske svar endte jeg opp med å lage DTO-en ved å bruke @Exclude() og @Expose() for å fjerne alt bortsett fra eksponerte egenskaper:

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

Og så laget jeg en generell transformasjonsinterceptor som kaller plainToclass for å konvertere objektet:

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

Og bruk deretter denne interceptoren til å transformere dataene til hvilken som helst type:

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

Som gir meg det jeg ønsket:

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

Føles definitivt mer rede-lignende! Jeg kan bruke den samme interceptoren hvor som helst jeg trenger, og for å endre responsen trenger jeg bare å endre DTO-ene.

Glade dager.

Svar #2

Et mulig alternativ er å merke DTO-objektet ditt med @Exclude og @Expose-dekoratørene, og deretter gjøre en konvertering med plainToClass:

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

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

Forutsatt at du har dekorert som ovenfor, kan du deretter gjøre: const dto=plainToClass(PhotoSnippetDto, photo);

Det resulterende objektet er i den formen du forventer, med kun id og name som vises på det endelige objektet. Hvis du senere bestemmer deg for å eksponere flere egenskaper, kan du enkelt legge dem til i ditt DTO og merke dem med @Expose.

Denne tilnærmingen lar deg også fjerne konstruktøren fra DTO-en din som bruker Object.assign

Relaterte artikler