Nest.js使用Axios发送POST参数请求报循环结构转JSON错误解决方法
问题根因
TypeError: Converting circular structure to JSON 报错的核心触发逻辑是:执行JSON序列化操作时,待序列化的对象存在循环引用(即对象属性直接/间接指向自身)。在NestJS + Axios的场景下,90%以上的该报错来自以下三种错误写法:
- 路由层直接将
@Req()拿到的完整请求对象传给Service层,该对象内置了request/response/socket等大量带循环引用的框架属性 - Service层调用Axios拿到响应后,直接将完整的Axios Response对象作为接口返回值,该对象自带
config/request属性,和自身形成循环引用,NestJS序列化返回值时直接抛错 - 参数拼接过程中误将框架上下文对象混入业务参数,传入序列化逻辑触发报错
正确实现方案
首先确认项目已安装@nestjs/axios依赖,并在业务模块中导入HttpModule:
// create.module.ts import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { CreateController } from './create.controller'; import { CreateService } from './create.service'; @Module({ imports: [HttpModule.register({ timeout: 5000 })], controllers: [CreateController], providers: [CreateService], }) export class CreateModule {}
1. create.validator.ts 参数校验层
从入口拦截非预期字段,确保传入Service的是纯业务数据,避免框架属性混入:
import { IsNumber, IsString, IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; // 按实际排班对象字段调整校验规则 class RosterItemDto { @IsNumber() staffId: number; @IsString() shift: string; } export class CreateReqDto { @IsNumber() id: number; @IsString() date: string; @IsArray() @ValidateNested({ each: true }) @Type(() => RosterItemDto) roster: RosterItemDto[]; }
2. create.controller.ts 路由层
只传递校验通过的纯请求体给Service,禁止传入完整的Req/Res框架对象:
import { Controller, Post, Body } from '@nestjs/common'; import { CreateService } from './create.service'; import { CreateReqDto } from './create.validator'; @Controller('api') export class CreateController { constructor(private readonly createService: CreateService) {} @Post('create') async create(@Body() reqBody: CreateReqDto) { return this.createService.submitToTarget(reqBody); } }
3. create.service.ts 业务逻辑层
核心注意两点:一是拼接参数时只处理纯业务字段,二是Axios请求结果只取data属性返回,禁止直接返回完整响应对象:
import { Injectable, BadRequestException } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; import { URLSearchParams } from 'url'; import { CreateReqDto } from './create.validator'; @Injectable() export class CreateService { constructor(private readonly httpService: HttpService) {} async submitToTarget(params: CreateReqDto) { const targetBaseUrl = 'https://create.com/api/index.php'; try { // 拼接URL参数逻辑,适配数组类型的roster字段 const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach(item => searchParams.append(`${key}[]`, JSON.stringify(item))); } else { searchParams.append(key, String(value)); } }); const fullTargetUrl = `${targetBaseUrl}?${searchParams.toString()}`; // 发起POST请求,如果需要传JSON格式body就把params放到post第二个参数位置 const { data } = await firstValueFrom( this.httpService.post(fullTargetUrl) ); return { code: 200, msg: '请求成功', result: data }; } catch (err) { throw new BadRequestException(`目标接口请求失败: ${err.message}`); } } }
避坑提示
- 禁止在参数、返回值中传入NestJS的Request/Response/ExecutionContext、Axios完整响应/错误对象,这类对象均存在循环引用,序列化时必然触发报错
- 若使用
JSON.stringify()手动处理参数,提前确认传入对象仅包含业务字段,无挂载的框架属性 - 数组类型参数拼接时不要直接做强制类型转换,按后端要求的数组传参格式处理即可
内容的提问来源于stack exchange,提问作者Rzj EM
相关产品推荐
相关产品推荐

