JavaScript导出JSON为Excel文件时触发r.forEach非函数类型错误
问题说明
使用JavaScript搭配xlsx库实现JSON数据导出Excel功能时,点击导出按钮触发以下报错,功能无法正常运行:
Uncaught TypeError: r.forEach is not a function at rb (xlsx.full.min.js:23:18346) at Object.tb [as json_to_sheet] (xlsx.full.min.js:23:19000) at downloadAsExcel (mains.js:61:32) at HTMLButtonElement.onclick (prepare.php:16:83)
涉及的现有代码和依赖如下:
涉及文件代码
prepear.php(报错信息中显示为prepare.php,存在文件名拼写不一致问题)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.2/xlsx.full.min.js"></script> <script src='https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js'></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <!-- <script src='excelexportjs.js'></script> --> <script src='main.js'></script> <script src="filesaver.js"></script> <div class="container mt-5 "> <pre id="json"></pre> <button class="btn btn-primary" onclick="downloadAsExcel(json.innerHTML)">download excel</button> </div> </head> <body> </body> </html>
main.js(报错信息中显示为mains.js,存在文件名拼写不一致问题,且代码未写完)
async function readFile(fileName) { console.log({ fileName }); return await Tesseract.recognize(fileName, 'ara', { logger: m => console.log(m) }); // fake it ... } async function parseDataFromTextAndPropertyNames(text, propertyNames) { console.log({ text, propertyNames }); return propertyNames .reduce((table, key) => Object.assign(table, { [ key ]: RegExp(`${ key }\W+(\w+)`) .exec(text)?.[1] ?? '' } ), {}); } async function writeParsedTextDataAsJSON(fileName, table) { console.log({ table }); JSON.stringify({ table }) // fake it ... return (await new Promise(resolve => setTimeout(() => { console.log({ fileName, json: JSON.stringify({ table }) }); resolve({ success: true }); }, 1500) ));
引入第三方依赖
- xlsx 0.16.2版本
- tesseract.js v2.1.0版本
- jQuery 3.6.0版本
- 本地filesaver.js脚本
根因分析
报错核心原因是XLSX.utils.json_to_sheet方法强制要求传入值为对象数组类型,方法内部会对传入的第一个参数调用forEach做行遍历,而现有代码直接传入<pre id="json">的innerHTML,属于字符串类型,字符串本身没有forEach方法,直接触发类型错误。
除此之外代码还存在3个附加问题:
- 现有main.js中未实现
downloadAsExcel方法,且文件名拼写和页面引用不一致,可能存在加载错误 - 传入的innerHTML是JSON格式的纯文本,没有先通过
JSON.parse()转换为可操作的JavaScript对象;就算完成解析,现有代码存储的JSON结构是{table: 数据数组}的嵌套对象,不是json_to_sheet要求的直接数组结构 - HTML结构错误:包含按钮的container div被写在了
<head>标签内部,正常DOM结构应该放在<body>标签内,可能导致元素加载异常
排查步骤
- 在downloadAsExcel方法第一行打印传入参数的类型和值,确认传入的是字符串而非目标数组
- 检查浏览器控制台的网络请求面板,确认main.js、filesaver.js没有404加载错误,文件名引用完全匹配
- 检查pre标签内写入的JSON结构,确认最终要导出的表格数据是标准对象数组格式,例:
[{列名1:值1, 列名2:值2}, {列名1:值3, 列名2:值4}] - 检查HTML结构,把head内的DOM元素移到body标签内部
修复方案
- 修正HTML结构错误,统一文件名拼写,调整按钮点击传参逻辑,修改后的页面代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.2/xlsx.full.min.js"></script> <script src='https://unpkg.com/tesseract.js@v2.1.0/dist/tesseract.min.js'></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src='main.js'></script> <script src="filesaver.js"></script> </head> <body> <div class="container mt-5 "> <pre id="json"></pre> <button class="btn btn-primary" onclick="handleDownload()">download excel</button> </div> </body> </html>
- 补全main.js中未写完的函数,新增downloadAsExcel方法,添加JSON解析、类型校验逻辑,修改后的完整main.js代码:
async function readFile(fileName) { console.log({fileName}); return await Tesseract.recognize(fileName, 'ara', { logger: m => console.log(m) }); } async function parseDataFromTextAndPropertyNames(text, propertyNames) { console.log({text, propertyNames}); return propertyNames.reduce((table, key) => Object.assign(table, { [key]: RegExp(`${key}\W+(\w+)`).exec(text)?.[1] ?? '' } ), {}); } async function writeParsedTextDataAsJSON(fileName, table) { console.log({table}); const jsonStr = JSON.stringify({table}); // 把生成的JSON内容写入pre标签 document.getElementById('json').textContent = jsonStr; return await new Promise(resolve => setTimeout(() => { console.log({ fileName, json: jsonStr }); resolve({ success: true }); }, 1500)); } // 实现Excel导出方法 function downloadAsExcel(exportData) { // 强校验传入数据必须是数组 if (!Array.isArray(exportData)) { throw new Error('导出数据必须为对象数组格式'); } // 数组转工作表 const worksheet = XLSX.utils.json_to_sheet(exportData); // 创建工作簿并挂载工作表 const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, '导出数据'); // 生成Excel文件二进制流 const excelBuffer = XLSX.write(workbook, {bookType:'xlsx', type:'array'}); // 转Blob触发下载 const blob = new Blob([excelBuffer], {type:'application/octet-stream'}); saveAs(blob, `导出数据_${new Date().getTime()}.xlsx`); } // 按钮点击处理逻辑 function handleDownload() { const jsonText = document.getElementById('json').textContent; try { // 把pre标签内的JSON字符串解析为JS对象 const parsedData = JSON.parse(jsonText); // 取出嵌套的表格数组 const tableData = parsedData.table; // 调用导出方法 downloadAsExcel(tableData); } catch (err) { console.error('导出失败:', err); alert('数据解析失败,请检查JSON格式是否正确'); } }
- 验证逻辑:先调用
writeParsedTextDataAsJSON写入测试数据到pre标签,再点击导出按钮即可正常生成Excel文件,不会再触发forEach类型错误。
内容的提问来源于stack exchange,提问作者Mohalzurghni
相关产品推荐
相关产品推荐

