Как сериализовать ответ nest js с классом-преобразователем при получении данных с помощью Typegoose?
Я пытался проработать пример NestJs для раздела сериализации для Mongodb, используя Typegoose, используя библиотеку преобразователей классов. Пример, приведенный на https://docs.nestjs.com/techniques/serialization, показывает только, как использовать сериализацию в TypeORM. Я проделал тот же процесс для Typegoose. Вот что я пробовал до сих пор.
// cat.domain.ts
import { prop } from '@typegoose/typegoose';
export class Cat {
@prop()
name: string;
@prop()
age: number;
@prop()
breed: string;
}
// cats.service.ts
@Injectable()
export class CatsService {
constructor(
@InjectModel(Cat) private readonly catModel: ReturnModelType<typeof Cat>,
) {}
findAll(): Observable<Cat[]> {
return from(this.catModel.find().exec());
}
findOne(id: string): Observable<Cat> {
return from(this.catModel.findById(id).exec());
}
...
}
// cat.response.ts
import { ObjectId } from 'mongodb';
import { Exclude, Transform } from 'class-transformer';
export class CatResponse {
@Transform(value => value.toString(), { toPlainOnly: true })
_id?: ObjectId;
name: string;
age: number;
@Exclude()
breed: string;
constructor(partial: Partial<CatResponse>) {
Object.assign(this, partial);
}
}
// cats.controller.ts
@Controller('cats')
@UseInterceptors(ClassSerializerInterceptor)
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll(): Observable<CatResponse[]> {
return this.catsService.findAll();
}
@Get(':id')
findOne(@Param() params: FindOneParamsDto): Observable<CatResponse> {
return this.catsService.findOne(params.id);
}
...
}
Я попытался запустить вызов API для Get() с идентификатором, но вместо breed
будучи исключенным из ответа, я получил следующий ответ.
{
"$__": {
"strictMode": true,
"selected": {},
"getters": {},
"_id": {
"_bsontype": "ObjectID",
"id": {
"type": "Buffer",
"data": [
94,
93,
76,
66,
116,
204,
248,
112,
147,
216,
167,
205
]
}
},
"wasPopulated": false,
"activePaths": {
"paths": {
"_id": "init",
"name": "init",
"age": "init",
"breed": "init",
"__v": "init"
},
"states": {
"ignore": {},
"default": {},
"init": {
"_id": true,
"name": true,
"age": true,
"breed": true,
"__v": true
},
"modify": {},
"require": {}
},
"stateNames": [
"require",
"modify",
"init",
"default",
"ignore"
]
},
"pathsToScopes": {},
"cachedRequired": {},
"$setCalled": [],
"emitter": {
"_events": {},
"_eventsCount": 0,
"_maxListeners": 0
},
"$options": {
"skipId": true,
"isNew": false,
"willInit": true
}
},
"isNew": false,
"_doc": {
"_id": {
"_bsontype": "ObjectID",
"id": {
"type": "Buffer",
"data": [
94,
93,
76,
66,
116,
204,
248,
112,
147,
216,
167,
205
]
}
},
"name": "Sylver",
"age": 14,
"breed": "Persian Cat",
"__v": 0
},
"$locals": {},
"$op": null,
"$init": true
}
Может ли кто-нибудь помочь мне с правильной сериализацией ответа?
3 ответа
это известная проблема (#108), typegoose (&mongoose) несовместим с class-transformer / class-
validator это потому, что typegoose должен преобразовать класс в схему, а mongoose скомпилирует его в модель (которая не является классом больше)
Для людей, пытающихся следовать документации nestjs и использовать mongoose, но
ClassSerializerInterceptor
не работает.
Публикация решения для использования class-transformer withe mongoose ниже, которое может быть полезно для других, использует собственный перехватчик, который вы можете увидеть в документации по гнезду. https://docs.nestjs.com/interceptors
How the folder structure would be:
src
└── cats
├── dto
│ └── cats-response.dto.ts
├── interceptor
│ └── cats.interceptor.ts
├── schemas
│ └── cat.schema.ts
├── cats.controller.ts
└── cats.service.ts
Мы создадим dto для ответа кошки, который называется
cats-response.dto.ts
и в нем исключитьbreed
свойство из ответа с помощью декоратора Exclude(). Используя Dto для ответа, мы создадим экземплярМы создадим собственный перехватчик для ответов кошек, вызываемый в
cats.interceptor.ts
. Вы можете сгенерировать его с помощью Nest cli, командаnest g interceptor cats
cat-response.dto.ts
Создать
CatsResponseDto
для использования в нашем пользовательском перехватчике.
import { Expose, Exclude } from 'class-transformer'
export class CatsResponseDto {
@Expose()
name: string;
@Expose()
age: number;
// Exclude decorator to exclude it from our response.
@Exclude()
breed: string;
}
Cats.interceptor.ts
Создание пользовательского
Примечание: plainToClass устарел и теперь называется plainToInstance, я узнал об этом, только когда использовал его в своей Ide, и он подсказал. Официальная документация еще не обновлена. https://github.com/typestack/class-transformer#plaintoclass
В журнале изменений упоминается об этом.
https://github.com/typestack/class-transformer/blob/develop/CHANGELOG.md#041-breaking-change---2021-11-20
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { plainToInstance } from 'class-transformer'
import { map, Observable } from 'rxjs';
import { CatsResponseDto } from '../dto/cats-response.dto'
@Injectable()
export class CatsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, handler: CallHandler): Observable<any> {
return handler.handle().pipe(
map((data: any) => {
// run something before the response is sent out.
// Please note that plainToClass is deprecated & is now called plainToInstance
return plainToInstance(CatsResponseDto, data, {
// By using excludeExtraneousValues we are ensuring that only properties decorated with Expose() decorator are included in response.
excludeExtraneousValues: true,
})
})
);
}
}
Наша схема cat должна быть определена, как показано ниже (для справки) в
cat.schema.ts
или аналогичный согласно документации мангуста.
cat.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
@Schema({ timestamps: true })
export class Cat {
// no Id defined here as its automatically added by mongoose unless we explicitly provide option to turn it OFF in schema options.
@Prop({ required: true })
name: string;
@Prop({ required: true })
age: number;
@Prop({ required: true })
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
Теперь привяжите наш пользовательский перехватчик
CatsInterceptor
в
cats.controller.ts
коты.controller.ts
import { Cat } from './schemas/cat.schema';
import { CatsInterceptor } from './interceptor/cats.interceptor';
import { CatsService } from './cats.service.ts'
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@UseInterceptors(CatsInterceptor)
@Get(':id')
findOne(@Param() params: FindOneParamsDto): Promise<Cat> {
return this.catsService.findOne(params.id);
}
...
}
РЕЗУЛЬТАТ : при вызове /cats/{id} ответ исключает породу.
связанная проблема: сериализация класса не работает вnesjs
Вот обходной путь:
// cats.controller.ts
...
import { classToPlain } from "class-transformer";
...
@Controller('cats')
@UseInterceptors(ClassSerializerInterceptor)
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll(): Observable<CatResponse[]> {
const cats = this.catsService.findAll();
// transforming the Model to CatResponse class...
const catResponses = cats.map(cat => classToPlain(new CatResponse(cat.toJSON())))
return catResponses;
}
@Get(':id')
findOne(@Param() params: FindOneParamsDto): Observable<CatResponse> {
const cat = this.catsService.findOne(params.id);
const catResponse = classToPlain(new CatResponse(cat.toJSON()));
return
}
...
}
Надеюсь, это поможет.