2

我开始在一个新的 NestJs 项目中工作,但是当我尝试实现序列化时遇到了一个问题。我想在网络响应中发送对象之前实现序列化以转换对象。我的项目工作正常,但是当我尝试在控制器中实现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)

我更改了ClassSerializerInterceptor的范围以解决问题,但错误仍然存​​在。根据文档,我需要在控制器中使用拦截器并在实体中使用相应的装饰器来实现序列化。我的序列化实现如下:

帐单-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

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[];
}

我不知道我做错了什么或错误的根源是什么。

4

2 回答 2

2

从我目前看到的情况来看,我想到了两件事。

  1. 扩展实体类的使用class-transformer和利用,class-validator以排除整个类的属性,只在生成的序列化对象中公开所需的属性。

代码会是这样的:

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. 另一种方法是将您的实体定义与返回的 dto 定义分开,这样您就可以扩展实体的定义,加上或减去返回的 dto 中想要的和不需要的属性。例如,假设您已将响应命名为 dto BillingStatementResponseDto,您将在控制器响应类型中使用它。BillingStatementResponseDto可以包含外部 api 对象的属性(例如,从一些外部 api 获取,您的一些实体属性和一些传入请求 dto 的属性。您还可以在定义 中的上述第一个建议中扩展使用class-transformer和使用class-validatorlike 。BillingStatementResponseDto

代码如下所示:

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(目标返回对象的新文件定义,使用class-transformerand class-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[];
}

帐单-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);
  }
}

恕我直言,第二种解决方案在层分离、灵活性、模块化和可维护性方面会更好:)

让我知道它是否有帮助;)

于 2019-12-24T13:16:07.520 回答
0

根据错误消息,我认为您有循环引用问题。只需注释掉您的 billing_statement 对象所指的其他对象,然后重试。如果这是您收到此错误的原因,您应该从子对象中删除对父对象的引用,或者尽量不要序列化这些引用。

祝你好运。

于 2019-12-25T12:18:26.257 回答