You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NodeJS对接Shopware6报Converting circular structure to JSON错误如何解决

问题分析与解决方案

根本原因

你遇到的循环序列化错误来自express-session模块的持久化逻辑,结合你的代码有两个核心触发点:

  • session对象被不可序列化的内容污染:你调用shopwareAuth.getToken时传入了完整的req.session对象,大概率该方法内部将Shopware API请求产生的ClientRequest、socket实例这类带循环引用的对象挂载到了session上,express-session尝试序列化session存储时触发循环引用报错。
  • 重复发送响应导致逻辑异常:你的axios回调逻辑中存在多次调用res.render的问题:请求出错时catch分支会调用一次render,后续的通用then分支无论成功失败都会再次调用render,重复响应会引发框架层异常,甚至可能将响应对象意外挂载到session上加剧问题。

修复步骤

  1. 调整shopwareAuth.getToken的传参逻辑:不要传入完整的req.session对象,如果该方法需要读写session内的token,仅传入需要的字符串字段即可,同时确保该方法不会将请求实例、socket这类非序列化对象挂载到session上,仅存纯字符串类型的accessToken即可。
  2. 修正响应逻辑,避免重复调用res.render:将成功场景的render放到axios的成功回调中,错误场景统一在catch中处理,避免重复发送响应。
  3. 排查session内容:可以在调用res.render前打印req.session的内容,确认是否存在非基础类型的挂载内容,及时清理。

修正后核心代码示例

exports.postNewJob = async function(req, res) {
    const jobOffer = {
        active              : true,
        UserId              : req.user.id,
        jobId               : req.body.jobName + "-" + req.body.marketId + "-" + req.body.jobType,
        jobTitle            : jobMapper.mapJobId(req.body.jobName) + jobMapper.mapJobType(req.body.jobType),
        storeId             : req.body.marketId,
        storeLocation       : req.body.marketName,
        jobType             : req.body.jobType,
        hoursWeek           : req.body.workingHours_Week? req.body.workingHours_Week : null,
        hoursMonth          : req.body.workingHours_Month? req.body.workingHours_Month : null,
        christmasBonus      : req.body.christmasBonus? true : false,
        holidayPay          : req.body.holidayPay? true : false,
        employeeDiscount    : req.body.employeeDiscount? true: false,
        freeDrinks          : req.body.freeDrinks? true : false,
        bav                 : req.body.bav? true : false,
        vwl                 : req.body.vwl? true : false,
        vacationAmount      : req.body.vacation,
        otherBenefits       : req.body.otherBenefits,
        contactMail         : req.body.contactEmail,
        contactPartner      : req.body.contactName,
        contactMarket       : req.body.contactPlace,
        contactStreet       : req.body.contactStreet + " ",
        contactStreetNumber : req.body.contactStreetNumber,
        contactZipCode      : req.body.contactPLZ + " ",
        contactCity         : req.body.contactCity,
        countryCode         : req.body.site,
        author              : req.user.emailAdress,
        hrLink              : req.body.hrLink,
        promoted            : false,
        promotionValue      : "",
        promotionBillingAdress : req.body.premiumBillingAdress
    };
    const premium = req.body.premium;
    const premiumLimit = req.body.premiumLimit;
    const premiumBillingAdress = req.body.premiumBillingAdress;
    const jobName = req.body.jobName;
    let jobTypes = [];

    try {
        jobTypes = await models.JobType.findAll();
        await models.JobPostingsMarket.findOrCreate({ where: {jobId: jobOffer.jobId}, defaults: jobOffer});
        console.log("Saved new job posting with following job id: " + jobOffer.jobId);

        // 调整传参,不传入完整session,不需要修改session就直接传空值
        const accessToken = await new Promise((resolve, reject) => {
            shopwareAuth.getToken(config.CLIENT_ID_SW, config.CLIENT_PASSWORD_SW, config.SHOPWARE_INSTANCE, req.user.emailAdress, 
            null, function(err, token){
                if(err) reject(err);
                else resolve(token);
            });
        });

        const requestBody = {
            'id': md5(jobOffer.jobId),
            'name': jobOffer.storeId + " - " + jobOffer.jobTitle,
            'url': jobOffer.jobId,
            'active': true,
            'salesChannels':[{'id':'XXX'}],
            'cmsPageId': 'XXX'
        };
        const headers = {
            'Authorization': 'Bearer ' + accessToken,
            'Content-Type': 'application/json',
        };    
        const options = {
            url: config.SHOPWARE_INSTANCE + '/api/landing-page/',
            method: 'POST',
            headers: headers,
            data: requestBody                 
        };

        await axios.request(options);
        console.log("Created new Job Landing Page with ID: " + jobOffer.jobId);
        // 成功仅调用一次render
        return res.render('app/market/jobs/form', {createJob : true, user : req.user, jobTypes : jobTypes});
    } catch (err) {
        console.error("操作失败:", err);
        // 所有错误场景统一处理,仅调用一次render/redirect
        if(!jobTypes || jobTypes.length === 0) {
            return res.redirect('/error');
        }
        return res.render('app/market/jobs/form', {createError : true, jobOffer, jobName, premium, premiumLimit, premiumBillingAdress, user : req.user, jobTypes : jobTypes});
    }
}

如果调整shopwareAuth.getToken传参成本较高,也可以临时给express-session配置自定义序列化逻辑过滤循环引用作为兜底方案:

const session = require('express-session');
const circularJson = require('circular-json');

app.use(session({
  // 原有配置保持不变
  secret: '你的session密钥',
  resave: false,
  saveUninitialized: false,
  // 新增自定义序列化方法
  stringify: (obj) => circularJson.stringify(obj),
  parse: (str) => circularJson.parse(str)
}));

注意兜底方案仅为临时解决报错,优先推荐排查session被污染的根源问题。

内容的提问来源于stack exchange,提问作者TheDude

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 17:36:01