【问题标题】:How to properly set up serialization with NestJS?如何使用 NestJS 正确设置序列化?
【发布时间】:2020-04-15 05:06:17
【问题描述】:

我开始在一个新的 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 的范围以解决问题,但错误仍然存​​在。根据documentation,我需要在控制器中使用拦截器,并在实体中使用相应的装饰器来实现序列化。我的序列化实现如下:

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

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

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

【问题讨论】:

    标签: javascript node.js nestjs class-transformer


    【解决方案1】:

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

    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-validator就像上面 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-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

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

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

    如果有帮助请告诉我 ;)

    【讨论】:

      【解决方案2】:

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

      祝你好运。

      【讨论】:

        猜你喜欢
        • 2020-10-31
        • 2021-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-22
        • 1970-01-01
        相关资源
        最近更新 更多