如何在使用 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
  1. 我们将在 cats-response.dto.ts 中为名为 CatsResponseDto 的猫响应创建一个 dto,并在其中使用 Exclude( ) 装饰器。通过使用 Dto 进行响应,我们将创建 CatsResponseDto

    的实例
  2. 我们将在 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} 响应时将排除品种。

相关问题: