如何在使用 Typegoose 获取数据时使用 class-transformer 序列化嵌套 js 响应?
How to serialize a nest js response with class-transformer while getting data with Typegoose?
我一直在尝试使用 Typegoose 和 class-transformer 库完成 Mongodb 序列化部分的 NestJs 示例。 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 使用 id 调用 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
}
谁能帮我正确序列化响应?
更新:
class-transformer 现在可以与 typegoose 一起正常工作,look here for the documentation on how to use it
this is an known issue (#108), typegoose (& mongoose) 与 class-transformer/class-validator
不兼容
这是因为 typegoose 需要将 class 转换为模式,而 mongoose 会将其编译为模型(不再是 class)
解决方法如下:
// 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
}
...
}
希望对您有所帮助。
对于尝试遵循 nestjs 文档并使用 mongoose 但 ClassSerializerInterceptor
不起作用的人。
在下面发布了使用 class-transformer withe mongoose 的解决方案,这可能对其他人有帮助,它使用自定义拦截器,您可以在 nest 文档中看到它。 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
我们将在 cats-response.dto.ts
中为名为 CatsResponseDto
的猫响应创建一个 dto,并在其中使用 Exclude( ) 装饰器。通过使用 Dto 进行响应,我们将创建 CatsResponseDto
的实例
我们将在 cats.interceptor.ts
中为名为 CatsInterceptor
的猫响应创建自定义拦截器。你可以使用nest cli生成它,命令是nest g interceptor cats
cats-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
创建自定义 CatsInterceptor
注意: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
中定义如下(供参考)或根据 mongoose 文档进行类似定义。
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);
现在在cats.controller.ts
中绑定我们的自定义拦截器CatsInterceptor
cats.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} 响应时将排除品种。
相关问题:
我一直在尝试使用 Typegoose 和 class-transformer 库完成 Mongodb 序列化部分的 NestJs 示例。 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 使用 id 调用 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
}
谁能帮我正确序列化响应?
更新: class-transformer 现在可以与 typegoose 一起正常工作,look here for the documentation on how to use it
this is an known issue (#108), typegoose (& mongoose) 与 class-transformer/class-validator
不兼容
这是因为 typegoose 需要将 class 转换为模式,而 mongoose 会将其编译为模型(不再是 class)
解决方法如下:
// 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
}
...
}
希望对您有所帮助。
对于尝试遵循 nestjs 文档并使用 mongoose 但 ClassSerializerInterceptor
不起作用的人。
在下面发布了使用 class-transformer withe mongoose 的解决方案,这可能对其他人有帮助,它使用自定义拦截器,您可以在 nest 文档中看到它。 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
我们将在
的实例cats-response.dto.ts
中为名为CatsResponseDto
的猫响应创建一个 dto,并在其中使用 Exclude( ) 装饰器。通过使用 Dto 进行响应,我们将创建CatsResponseDto
我们将在
cats.interceptor.ts
中为名为CatsInterceptor
的猫响应创建自定义拦截器。你可以使用nest cli生成它,命令是nest g interceptor cats
cats-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
创建自定义 CatsInterceptor
注意: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
中定义如下(供参考)或根据 mongoose 文档进行类似定义。
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);
现在在cats.controller.ts
CatsInterceptor
cats.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} 响应时将排除品种。
相关问题: