最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to properly set up serialization with NestJS? - Stack Overflow

programmeradmin4浏览0评论

I started to work in a new NestJs project but I'm facing an issue when I try to implement serialization. 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:

[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.<puted> (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 started to work in a new NestJs project but I'm facing an issue when I try to implement serialization. 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:

[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.<puted> (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. 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

import { ClassSerializerInterceptor, Controller, Get, Query, UseInterceptors } from '@nestjs/mon';
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[];
}

I don't know what I'm doing wrong or what is the source of the error.

Share Improve this question asked Dec 24, 2019 at 2:52 ELopezELopez 651 gold badge1 silver badge8 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

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.

Code would like this:

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. E.g, let's say you have named your response dto BillingStatementResponseDto, you would use this one in your controller response type. BillingStatementResponseDto could contain external api object's attributes (fetch from some external api for exampleà, some of your entity attributes and some of the ining 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.

Code would look like this:

billing-statement.entity.ts (remains the same, without class-transformer stuff)

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

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/mon';
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 ment out the other objects that your billing_statement object is referring to , and then try again. 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.

发布评论

评论列表(0)

  1. 暂无评论