问题
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