问题
I've a DTO for my Photo and Tag object that looks like this:
export class PhotoDto {
readonly title: string
readonly file: string
readonly tags: TagDto[]
}
export class TagDto {
readonly name: string
}
I use the PhotoDto in my photo.service.ts and eventually in the photo.controller.ts for the creation of Photo:
// In photo.service.ts
async create(createPhotoDto: PhotoDto): Promise<PhotoEntity> {
// ...
return await this.photoRepo.create(createPhotoDto)
}
// In photo.controller.ts
@Post()
async create(@Body() createPhotoDto: PhotoDto): Promise<PhotoEntity> {
// ...
}
However, the input in the Body of the API is expected to have this structure:
{
"title": "Photo Title",
"file": "/some/path/file.jpg",
"tags": [
{
"name": "holiday"
},
{
"name": "memories"
}
]
}
How can I change the input shape of the Body to accept this structure instead?
{
"title": "Photo Title",
"file": "/some/path/file.jpg",
"tags": ["holiday", "memories"]
}
I have tried creating 2 different DTOs, a CreatePhotoDto and an InputPhotoDto, one for the desired input shape in the controller and one for use with the service and entity, but this ends up very messy because there is a lot of work with converting between the 2 DTOs.
What is the correct way to have different input shape from the Body of a Post request and then have it turned into the DTO required for use by the entity?
回答1:
You can use the auto-transform of the ValidationPipe():
1) Add the ValidationPipe to your controller:
@UsePipes(new ValidationPipe({ transform: true }))
@Post()
async create(@Body() createPhotoDto: PhotoDto): Promise<PhotoEntity> {
// ...
}
2) Add a @Transform to your PhotoDto:
// Transforms string[] to TagDto[]
const transformTags = tags => {
if (Array.isArray(tags)) {
return tags.map(tag => ({name: tag}))
} else {
return tags;
}
}
import { Transform } from 'class-transformer';
export class PhotoDto {
readonly title: string
readonly file: string
@Transform(transformTags, {toClassOnly: true})
readonly tags: TagDto[]
}
回答2:
You can create a nest custom decorator to convert input data to your DTO object.
export const ConvertToCreateCatDto = createRouteParamDecorator((data, req): CreateCatDto => { // `createParamDecorator` for nest old version
if (req.body.tags.every(value => typeof value === "string")) { // if input tags is a string[]
req.body.tags = (req.body.tags as string[]).map<TagDto>((tag) => {
return { // convert to TagDto
name: tag + ""
}
});
}
let result = new CreateCatDto(req.body);
// TODO: validate `result` object
return result;
});
add constructor to CreateCatDto
export class CreateCatDto {
readonly title: string;
readonly file: number;
readonly tags: TagDto[];
constructor(obj: any) {
this.title = obj.title;
this.file = obj.file;
this.tags = obj.tags;
}
}
Finally, use @ConvertToCreateCatDto instead of @Body in you controller.
// In photo.controller.ts
@Post()
async create(@ConvertToCreateCatDto() createPhotoDto: PhotoDto): Promise<PhotoEntity> {
//...
}
回答3:
Update DTO to
export class PhotoDto {
readonly title: string
readonly file: string
readonly tags: Array<string>
}
It will change the API structure to
{
"title": "Photo Title",
"file": "/some/path/file.jpg",
"tags": ["holiday", "memories"]
}
currently your tags property is an array of object of type TagDto, change tags property to just array of string.
来源:https://stackoverflow.com/questions/55448050/nestjs-how-to-have-body-input-shape-different-from-entitys-dto