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
