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上加剧问题。
修复步骤
- 调整
shopwareAuth.getToken的传参逻辑:不要传入完整的req.session对象,如果该方法需要读写session内的token,仅传入需要的字符串字段即可,同时确保该方法不会将请求实例、socket这类非序列化对象挂载到session上,仅存纯字符串类型的accessToken即可。 - 修正响应逻辑,避免重复调用
res.render:将成功场景的render放到axios的成功回调中,错误场景统一在catch中处理,避免重复发送响应。 - 排查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
相关产品推荐
相关产品推荐

