简体   繁体   English

如何使用 NestJS 正确设置序列化?

[英]How to properly set up serialization with NestJS?

I started to work in a new NestJs project but I'm facing an issue when I try to implement serialization.我开始在一个新的 NestJs 项目中工作,但是当我尝试实现序列化时遇到了一个问题。 I want to implement serialization to transform objects before they gets sent in a network response.我想在对象被发送到网络响应之前实现序列化来转换对象。 My project was working correctly but when I tried to implement ClassSerializerInterceptor in my controller I got the following error:我的项目工作正常,但是当我尝试在控制器中实现ClassSerializerInterceptor 时,出现以下错误:

 [Nest] 27010 - 12/23/2019, 8:20:53 PM [ExceptionsHandler] Maximum call stack size exceeded +29851ms RangeError: Maximum call stack size exceeded at Object.Console.<computed> (internal/console/constructor.js:241:9) at Object.log (internal/console/constructor.js:282:26) at Object.consoleCall (<anonymous>) at _loop_1 (/path/to/my/project/node_modules/class-transformer/TransformOperationExecutor.js:146:47)

I changed the scope of ClassSerializerInterceptor to solve the problem but the error persists.我更改了ClassSerializerInterceptor的范围来解决问题,但错误仍然存​​在。 According to the documentation , I need to use the interceptor in a controller and use the corresponding decorators in an entity to implement serialization.根据文档,我需要在控制器中使用拦截器并在实体中使用相应的装饰器来实现序列化。 My implementation of serialization is the following:我的序列化实现如下:

billing-statement.controller.ts billing-statement.controller.ts

 import { ClassSerializerInterceptor, Controller, Get, Query, UseInterceptors } from '@nestjs/common'; import { BillingStatementService } from './billing-statement.service'; import { BillingStatementDto } from './billing-statement.dto'; import { BillingStatement } from './billing-statement.entity'; @Controller('billing-statement') export class BillingStatementController { constructor(private readonly billingStatementService: BillingStatementService) {} @Get() @UseInterceptors(ClassSerializerInterceptor) async getBillingStatement( @Query() query: BillingStatementDto, ): Promise<BillingStatement> { return this.billingStatementService.findBillingStatementByUser(+query.id); } }

billing-statement.entity.ts billing-statement.entity.ts

 import { AutoIncrement, BelongsTo, Column, ForeignKey, HasMany, Model, PrimaryKey, Table } from 'sequelize-typescript'; import { User } from '../users/user.entity'; import { Payment } from './payment.entity'; import { Exclude } from 'class-transformer'; @Table({ tableName: 'billing_statement_tbl', timestamps: false, }) export class BillingStatement extends Model<BillingStatement> { @AutoIncrement @PrimaryKey @Column({field: 'billing_statement_id_pk'}) id: number; @Column currency: string; @Column({field: 'total_amount'}) totalAmount: number; @Exclude() @Column({field: 'contract_start'}) contractStart: Date; @Exclude() @Column({field: 'contract_end'}) contractEnd: Date; @HasMany(() => Payment) payments: Payment[]; }

I don't know what I'm doing wrong or what is the source of the error.我不知道我做错了什么或错误的根源是什么。

From what I've seen so far, two things came to my mind.从我目前看到的情况来看,我想到了两件事。

  1. Extend use of class-transformer and make use of class-validator within entity class in order to exclude the whole class' properties and only expose the wanted ones in your resulting serialized object.扩展class-transformer class-validator使用,并在实体类中使用class-validator ,以排除整个类的属性,并仅在生成的序列化对象中公开所需的属性。

Code would like this:代码是这样的:

billing-statement.entity.ts billing-statement.entity.ts

import { AutoIncrement, BelongsTo, Column, ForeignKey, HasMany, Model, PrimaryKey, Table } from 'sequelize-typescript';
import { User } from '../users/user.entity';
import { Payment } from './payment.entity';
import { Exclude, Expose, Type } from 'class-transformer';
import { IsArray, IsNumber, IsString } from 'class-validator';

@Exclude()
@Table({
  tableName: 'billing_statement_tbl',
  timestamps: false,
})
export class BillingStatement extends Model<BillingStatement> {
  @AutoIncrement
  @PrimaryKey
  @Column({field: 'billing_statement_id_pk'})
  @Expose()
  @IsNumber()
  id: number;

  @Column
  @Expose()
  @IsString()
  currency: string;

  @Column({field: 'total_amount'})
  @Expose()
  @IsNumber()
  totalAmount: number;

  @Column({field: 'contract_start'})
  contractStart: Date;

  @Column({field: 'contract_end'})
  contractEnd: Date;

  @HasMany(() => Payment)
  @IsArray()
  @Expose()
  @Type(() => Payment)
  payments: Payment[];
}
  1. Another way would be to split your entity definition from the returned dto definition, in that way you can extend the definition of your entity, plus or minus wanted and unwanted properties in the returned dto.另一种方法是将您的实体定义与返回的 dto 定义分开,这样您就可以扩展实体的定义,加上或减去返回的 dto 中想要和不需要的属性。 Eg, let's say you have named your response dto BillingStatementResponseDto , you would use this one in your controller response type.例如,假设您将响应命名为 dto BillingStatementResponseDto ,您将在控制器响应类型中使用这个。 BillingStatementResponseDto could contain external api object's attributes (fetch from some external api for exampleà, some of your entity attributes and some of the incoming request dto's properties as well. You would also extend use of class-transformer and make use of class-validator like in 1st advice above in the BillingStatementResponseDto definition. BillingStatementResponseDto可以包含外部 api 对象的属性(例如从一些外部 api 获取,一些实体属性和一些传入请求 dto 的属性。您还可以扩展class-transformer class-validator使用并使用class-validatorBillingStatementResponseDto定义中的第一条建议。

Code would look like this:代码如下所示:

billing-statement.entity.ts (remains the same, without class-transformer stuff) billing-statement.entity.ts (保持不变,没有类转换器的东西)

import { AutoIncrement, BelongsTo, Column, ForeignKey, HasMany, Model, PrimaryKey, Table } from 'sequelize-typescript';
import { User } from '../users/user.entity';
import { Payment } from './payment.entity';

@Table({
  tableName: 'billing_statement_tbl',
  timestamps: false,
})
export class BillingStatement extends Model<BillingStatement> {
  @AutoIncrement
  @PrimaryKey
  @Column({field: 'billing_statement_id_pk'})
  id: number;

  @Column
  currency: string;

  @Column({field: 'total_amount'})
  totalAmount: number;

  @Column({field: 'contract_start'})
  contractStart: Date;

  @Column({field: 'contract_end'})
  contractEnd: Date;

  @HasMany(() => Payment)
  payments: Payment[];
}

billing-statement-response.dto.ts (new file definition for your targeted returned object, with use of class-transformer and class-validator ) - to be imported and used in your controller billing-statement-response.dto.ts (目标返回对象的新文件定义,使用class-transformerclass-validator ) - 导入并在您的控制器中使用

import { Exclude, Expose, Type } from 'class-transformer';
import { IsArray, IsNumber, IsString, ValidateNested } from 'class-validator';

@Exclude()
export class BillingStatementResponseDto {
  @Expose()
  @IsNumber()
  id: number;

  @Expose()
  @IsString()
  currency: string;

  @Expose()
  @IsNumber()
  totalAmount: number;

  @IsArray()
  @ValidateNested()
  @Expose()
  @Type(() => Payment)
  payments: Payment[];
}

billing-statement.controller.ts billing-statement.controller.ts

import { ClassSerializerInterceptor, Controller, Get, Query, UseInterceptors } from '@nestjs/common';
import { BillingStatementService } from './billing-statement.service';
import { BillingStatementDto } from './billing-statement.dto';
import { BillingStatementResponseDto } from './billing-statement-response.dto'; // <= import your newly defined dto 

@Controller('billing-statement')
export class BillingStatementController {
  constructor(private readonly billingStatementService: BillingStatementService) {}

  @Get()
  @UseInterceptors(ClassSerializerInterceptor)
  async getBillingStatement(
    @Query() query: BillingStatementDto,
  ): Promise<BillingStatementResponseDto> { // <= here you go for the use of BillingStatementResponseDto
    return this.billingStatementService.findBillingStatementByUser(+query.id);
  }
}

IMHO, second solution would be better in terms of layers separation, flexibility, modularity and maintainability :)恕我直言,第二个解决方案在层分离、灵活性、模块化和可维护性方面会更好:)

Let me know if it helps ;)如果有帮助,请告诉我;)

Based on the error message, I think you have a circular reference issue.根据错误消息,我认为您有循环引用问题。 Just comment out the other objects that your billing_statement object is referring to , and then try again.只需注释掉 billing_statement 对象所指的其他对象,然后再试一次。 If that is the reason you are getting this error, you should remove reference from child objects to the parent object or try not to serialize those references.如果这就是您收到此错误的原因,您应该删除从子对象到父对象的引用,或者尽量不要序列化这些引用。

Best of luck.祝你好运。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM