
JSON 广泛用于 Web 应用与 API 之间的数据交换,但在查看或共享记录时,它并不总是最方便的格式。将 JSON 导出为 Excel,可以把数据整理成工作表,方便用户排序、编辑以及用于报表。
本教程介绍如何结合 JavaScript 和 Spire.XLS for JavaScript,在 React 应用中将 JSON 转换为 Excel。内容涵盖简单的员工记录数组,以及导出前需要扁平化的嵌套 JSON。你还将了解如何保留基本数据类型、设置工作表格式,并在浏览器中下载生成的 XLSX 文件。
目录:
- 在 React 中配置 Spire.XLS for JavaScript
- 在 React 中将简单 JSON 数组转换为 Excel
- 在 React 中将嵌套 JSON 转换为 Excel
- 常见问题及解决方法
- 常见问答
- 总结
1. 在 React 中配置 Spire.XLS for JavaScript
Spire.XLS for JavaScript 提供了创建工作簿、写入单元格值、应用样式以及保存 Excel 文件的 API。在本教程的工作流中,JavaScript 负责解析和整理 JSON 数据,Spire.XLS 则负责构建 Excel 工作簿。
在 React 项目中安装以下包:
npm i spire.office
将包中对应的运行时文件复制到项目的 public 文件夹:spire.xls.js、Spire.Xls.Wasm.zip、spire.common.js、Spire.Common.Wasm.zip,以及 _framework 文件夹。确保这些文件来自同一版本的包。详细配置步骤请参阅如何在 React 项目中集成 Spire.XLS for JavaScript。
JSON 输入文件也放在 public 文件夹中。下面的示例分别使用 employees.json 和 employees_nested.json。
React 组件在 useEffect() 中加载 spire.xls.js,并将初始化后的模块保存在状态中。初始化完成前,转换按钮保持禁用状态。
项目配置: 示例沿用所提供 React 组件中的 process.env.PUBLIC_URL 和基于 webpack 的加载方式。如果项目使用 Vite 或其他构建工具,需要根据相应环境调整公共资源 URL 和动态导入配置。
2. 在 React 中将简单 JSON 数组转换为 Excel
扁平的对象数组可以直接映射为工作表:属性名作为列标题,每个对象对应一行数据。
准备 JSON 文件
将以下内容保存为 public/employees.json:
[
{
"employee_id": "E001",
"name": "简·多伊",
"department": "人力资源部",
"salary": 85000.5,
"active": true,
"hire_date": "2022-03-15"
},
{
"employee_id": "E002",
"name": "迈克尔·史密斯",
"department": "信息技术部",
"salary": 96000,
"active": true,
"hire_date": "2021-07-01"
},
{
"employee_id": "E003",
"name": "萨拉·林",
"department": "财务部",
"salary": 92000.75,
"active": false,
"hire_date": "2023-01-12"
},
{
"employee_id": "E004",
"name": "李伟",
"department": "运营部",
"salary": 81500,
"active": true,
"hire_date": "2020-11-30"
},
{
"employee_id": "E005",
"name": "安娜·彼得罗娃",
"department": "市场部",
"salary": 78000,
"active": true,
"hire_date": "2019-08-19"
},
{
"employee_id": "E006",
"name": "戴维·陈",
"department": null,
"salary": 0,
"active": false
}
]
此示例包含字符串、数字、布尔值、中文字符、null 值以及缺失属性。这些情况可以展示导出过程中如何处理不同的值,同时保留 0 和 false。
将 JSON 转换为 Excel
转换过程如下:
- 将 JSON 文件加载到虚拟文件系统(VFS),以 UTF-8 解码文件内容,并使用
JSON.parse()解析。 - 创建工作簿,移除默认工作表,然后添加名为“员工”的工作表。
- 将第一个对象的键写入表头行。
- 将每条记录写入新的一行,根据 JavaScript 值的类型使用
NumberValue、BooleanValue或Text。 - 设置表头样式,根据内容估算列宽,并添加细边框。
- 将工作簿保存到 VFS,读取生成的字节数据,并下载为 XLSX 文件。
完整 React 示例
使用以下组件替换 App.js 的内容:
import React, { useState, useEffect } from 'react';
function App() {
const [wasmModule, setWasmModule] = useState(null);
// 加载 Spire.XLS
useEffect(() => {
(async () => {
try {
const publicUrl = process.env.PUBLIC_URL || '';
const spireModule = await import(/* webpackIgnore: true */ `${publicUrl}/spire.xls.js`);
const rawModule = spireModule.default || spireModule;
window.wasmModule = typeof rawModule === 'function'
? await rawModule({ locateFile: p => p.endsWith('.wasm') ? `${publicUrl}/${p}` : p })
: rawModule;
setWasmModule(window.wasmModule);
} catch (error) {
console.error('加载 spire.xls.js WASM 模块失败:', error);
}
})();
}, []);
// 将 JSON 数据转换为 Excel 文件
const JsonToExcel = async () => {
const spirexls = wasmModule?.spirexls;
if (!spirexls) {
console.error('Spire.XLS 尚未初始化。');
return;
}
let workbook;
try {
// 1. 将 JSON 文件加载到虚拟文件系统(VFS)
const inputFileName = 'employees.json';
const inputVfsPath = await window.spire.FetchFileToVFS(
inputFileName,
'/',
`${process.env.PUBLIC_URL || ''}/`
);
// 2. 以 UTF-8 文本读取文件,并解析为 JavaScript 对象
const jsonBytes = window.dotnetRuntime.Module.FS.readFile(inputVfsPath);
const jsonText = new TextDecoder('utf-8').decode(jsonBytes);
const data = JSON.parse(jsonText);
// 检查输入是否为空或结构是否无效
if (!Array.isArray(data) || data.length === 0) {
throw new Error('JSON 必须是非空的对象数组。');
}
// 3. 创建新的工作簿和工作表
workbook = new spirexls.Workbook();
workbook.Worksheets.Clear();
const sheet = workbook.Worksheets.Add('员工');
// 4. 根据第一条记录的键写入表头(第 1 行)
const headers = Object.keys(data[0]);
headers.forEach((header, col) => {
const cell = sheet.get(1, col + 1); // 索引从 1 开始
cell.Text = header;
cell.Style.Font.IsBold = true;
cell.Style.Font.Size = 12;
cell.Style.Font.Color = spirexls.Color.get_White();
cell.Style.Color = spirexls.Color.get_DarkBlue();
});
// 5. 从第 2 行开始写入数据,保留正确的单元格类型
data.forEach((record, rowIdx) => {
headers.forEach((key, colIdx) => {
const cell = sheet.get(rowIdx + 2, colIdx + 1);
const value = record[key];
if (typeof value === 'number') {
cell.NumberValue = value;
} else if (typeof value === 'boolean') {
cell.BooleanValue = value;
} else {
cell.Text = value == null ? '' : String(value);
}
});
});
// 6. 显式设置列宽
const widths = headers.map((header) => {
let maxLen = header.length;
data.forEach(record => {
const v = record[header];
maxLen = Math.max(maxLen, v == null ? 0 : String(v).length);
});
return Math.min(Math.max(maxLen + 4, 10), 40);
});
const columns = sheet.Columns;
widths.forEach((w, i) => { columns[i].ColumnWidth = w; }); // 索引从 0 开始
// 7. 为使用区域添加细边框
const usedRange = sheet.get(1, 1, data.length + 1, headers.length);
usedRange.Borders.LineStyle = spirexls.LineStyleType.Thin;
usedRange.Borders.Color = spirexls.Color.get_LightSteelBlue();
// 8. 将工作簿保存到 VFS
const outputFileName = 'employees.xlsx';
workbook.SaveToFile({
fileName: outputFileName,
version: spirexls.ExcelVersion.Version2016
});
// 9. 读取保存的文件,转换为 Blob 并触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const modifiedFile = new Blob([modifiedFileArray], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const url = URL.createObjectURL(modifiedFile);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (error) {
console.error('JSON 转 Excel 失败:', error);
} finally {
// 即使转换过程中失败,也释放资源。
try {
workbook?.Dispose();
} catch (cleanupError) {
console.warn('释放工作簿资源失败:', cleanupError);
}
}
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>在 React 中使用 JavaScript 将 JSON 转换为 Excel</h1>
<button onClick={JsonToExcel} disabled={!wasmModule}>
转换
</button>
</div>
);
}
export default App;
代码中,sheet.get() 的行、列索引从 1 开始,而 sheet.Columns 集合的索引从 0 开始。列宽根据字符串长度估算,最大为 40;这是一种简单的尺寸设置规则,并非对实际渲染文本宽度的精确测量。
输出效果:
下图展示生成的 employees.xlsx 文件,其中包含设置了样式的表头行和六条员工记录。数字和布尔值保留各自的单元格类型,中文字符保持不变,null 或缺失值显示为空白单元格。日期字符串保留原来的 YYYY-MM-DD 文本格式。

JSON 没有原生日期类型,此代码也不会将日期字符串转换为 Excel 日期。此外,表头来自第一条记录,因此仅出现在后续记录中的属性不会被导出。下一个示例将收集所有记录的字段,以解决这一限制。
3. 在 React 中将嵌套 JSON 转换为 Excel
嵌套对象无法直接映射为单个工作表单元格。导出前,需要将其属性展开到同一层级,并确定数组在工作表中的呈现方式。
在本示例中,嵌套对象的路径转换为以点号分隔的列名,简单值数组转换为以逗号分隔的文本。每名员工仍占一行。
准备嵌套 JSON 示例
将以下内容保存为 public/employees_nested.json:
[
{
"employee_id": "E001",
"name": "简·多伊",
"salary": 85000.5,
"active": true,
"address": {
"city": "西雅图",
"country": "美国"
},
"skills": ["招聘", "培训"]
},
{
"employee_id": "E002",
"name": "迈克尔·史密斯",
"salary": 96000,
"active": true,
"address": {
"city": "伦敦",
"country": "英国",
"postal_code": "SW1A 1AA"
},
"skills": ["JavaScript 开发", "React 开发"]
},
{
"employee_id": "E004",
"name": "李伟",
"salary": 81500,
"active": false,
"address": {
"city": "上海",
"country": "中国"
},
"skills": []
}
]
postal_code 属性仅出现在第二名员工的地址中。因此,导出时需要从所有扁平化后的记录中收集列名,而不能只使用第一条记录的字段。
将嵌套对象扁平化并处理数组
在现有 App.js 文件的 function App() 上方添加以下辅助函数:
function isRecord(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function flattenRecord(record) {
// 使用无原型对象安全地存储任意 JSON 属性名。
const result = Object.create(null);
const visit = (value, path) => {
if (Array.isArray(value)) {
const containsComplexValues = value.some(
item => item !== null && typeof item === 'object'
);
// 将复杂数组保留为 JSON 文本;拼接简单数组,便于阅读。
result[path] = containsComplexValues
? JSON.stringify(value)
: value.map(item => item == null ? '' : String(item)).join(', ');
} else if (isRecord(value)) {
const entries = Object.entries(value);
if (entries.length === 0 && path) {
result[path] = '';
} else {
entries.forEach(([key, child]) => {
visit(child, path ? `${path}.${key}` : key);
});
}
} else {
result[path] = value;
}
};
visit(record, '');
return result;
}
第一名员工扁平化后的对象等同于:
{
"employee_id": "E001",
"name": "简·多伊",
"salary": 85000.5,
"active": true,
"address.city": "西雅图",
"address.country": "美国",
"skills": "招聘, 培训"
}
数组以外的数字和布尔值保持原类型。空数组转换为空文本。如果数组包含对象或其他数组,辅助函数会将其保存为 JSON 文本,避免产生缺乏实际意义的 [object Object] 字符串。
这种以点号分隔的命名方式假定原始属性名不包含点号。如果数据中存在 "address.city" 这样的字面键名,应采用带转义的路径规则或显式列映射,避免名称冲突。将数组转换为文本是为了方便阅读,并不适合无损重建原始 JSON。
将扁平化后的数据导出为 Excel
保留简单示例中的模块初始化代码和组件布局,将其中的 JsonToExcel 处理函数替换为以下代码。该函数加载嵌套 JSON 文件,将各条记录扁平化,收集所有可用字段,并导出生成的表格:
const JsonToExcel = async () => {
const spirexls = wasmModule?.spirexls;
if (!spirexls) {
console.error('Spire.XLS 尚未初始化。');
return;
}
let workbook;
try {
// 1. 加载并解析嵌套 JSON 文件
const inputVfsPath = await window.spire.FetchFileToVFS(
'employees_nested.json',
'/',
`${process.env.PUBLIC_URL || ''}/`
);
const bytes = window.dotnetRuntime.Module.FS.readFile(inputVfsPath);
const data = JSON.parse(new TextDecoder('utf-8').decode(bytes));
if (!Array.isArray(data) || data.length === 0 || !data.every(isRecord)) {
throw new Error('JSON 必须是非空的对象数组。');
}
// 2. 将记录扁平化,并收集所有记录的字段
const flatData = data.map(flattenRecord);
const headers = [...new Set(flatData.flatMap(record => Object.keys(record)))];
if (headers.length === 0) {
throw new Error('JSON 记录中没有可导出的字段。');
}
// 3. 创建工作簿和工作表
workbook = new spirexls.Workbook();
workbook.Worksheets.Clear();
const sheet = workbook.Worksheets.Add('员工');
// 4. 写入表头并设置样式
headers.forEach((header, col) => {
const cell = sheet.get(1, col + 1);
cell.Text = header;
cell.Style.Font.IsBold = true;
cell.Style.Font.Size = 12;
cell.Style.Font.Color = spirexls.Color.get_White();
cell.Style.Color = spirexls.Color.get_DarkBlue();
});
// 5. 写入扁平化后的值,并保留标量类型
flatData.forEach((record, row) => {
headers.forEach((key, col) => {
const cell = sheet.get(row + 2, col + 1);
const value = record[key];
if (typeof value === 'number') {
cell.NumberValue = value;
} else if (typeof value === 'boolean') {
cell.BooleanValue = value;
} else {
cell.Text = value == null ? '' : String(value);
}
});
});
// 6. 估算列宽并添加边框
const columns = sheet.Columns;
headers.forEach((header, col) => {
let maxLen = header.length;
flatData.forEach(record => {
const value = record[header];
maxLen = Math.max(maxLen, value == null ? 0 : String(value).length);
});
columns[col].ColumnWidth = Math.min(Math.max(maxLen + 4, 10), 40);
});
const usedRange = sheet.get(1, 1, flatData.length + 1, headers.length);
usedRange.Borders.LineStyle = spirexls.LineStyleType.Thin;
usedRange.Borders.Color = spirexls.Color.get_LightSteelBlue();
// 7. 保存并下载工作簿
const outputFileName = 'employees_nested.xlsx';
workbook.SaveToFile({
fileName: outputFileName,
version: spirexls.ExcelVersion.Version2016
});
const outputBytes = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([outputBytes], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = outputFileName;
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(url);
} catch (error) {
console.error('嵌套 JSON 转 Excel 失败:', error);
} finally {
try {
workbook?.Dispose();
} catch (cleanupError) {
console.warn('释放工作簿资源失败:', cleanupError);
}
}
};
输出效果:

生成的工作表将 address.city、address.country 和 address.postal_code 分别作为独立列。技能以可读文本显示在同一个单元格中,没有邮政编码的员工在对应列中留空。表头顺序取决于字段在各条记录中首次出现的顺序。
如果对象数组中的每个元素都需要单独分析,将其导出到独立工作表,或按照明确的一对多规则展开为多行,通常比保存为 JSON 文本更实用。这种方式需要确定哪些父级字段应重复,以及如何关联相关记录。
4. 常见问题及解决方法
| 问题 | 解决方法 |
|---|---|
| 转换按钮一直处于禁用状态 | 检查浏览器控制台和 Network 面板中的模块加载错误。确认运行时文件可以访问,并且来自同一版本的包。 |
| 无法加载 JSON 文件 | 确认文件位于 public 文件夹中,且公共资源 URL 指向正确位置。如果返回的是 HTML 错误页面而非 JSON,也可能导致解析错误。 |
JSON.parse() 抛出错误 |
检查 JSON 格式是否无效,是否存在注释、末尾多余逗号或非预期的响应内容。 |
| 输入为空或结构不正确 | 提供非空的对象数组。如果 API 响应将数组包裹在一个对象中,应在导出前选取对应的数组属性。 |
| 部分列缺失 | 按照嵌套示例收集所有记录的键的并集,而不是仅读取 data[0]。 |
单元格显示 [object Object] |
写入前将嵌套对象扁平化,或明确使用 JSON.stringify() 将复杂值序列化。 |
| 日期在 Excel 中被视为文本 | 示例将日期字符串作为文本写入。如果需要进行日期计算,应添加显式日期解析,并设置 Excel 日期格式。 |
简单示例只验证输入是否为非空数组,并不检查每个成员的类型。对于结构不确定的输入,应采用嵌套示例中更严格的 data.every(isRecord) 验证,并在没有可用字段时拒绝导出。
5. 常见问答
可以将 API 响应中的 JSON 转换为 Excel 吗?
可以。使用 fetch() 和 response.json() 获取数据,然后将得到的数组传入相同的工作表写入流程。如果响应具有 { "employees": [...] } 这样的外层结构,应先选取 responseData.employees。当数据已经是 JavaScript 对象时,无需再将静态 JSON 文件加载到 VFS。
如何处理字段不同的记录?
根据所有记录的键构建列列表。嵌套示例使用 Set 按字段首次出现的顺序收集不重复的字段名。如果某条记录不包含某列对应的字段,代码会向该单元格写入空文本。
如何导出嵌套对象和数组?
将嵌套对象展开为基于路径的列,例如 address.city。对于简单数组,可将值拼接到一个单元格中,让每条父级记录保持一行。对象数组则可以根据实际使用需求保存为 JSON 文本、展开为多行,或导出到独立工作表。
Excel 中会保留数字和布尔值吗?
会。示例将 JavaScript 数字赋给 NumberValue,将布尔值赋给 BooleanValue。字符串仍保持为文本,包括看起来像数字的标识符和日期字符串。空值检查使用 value == null,因此 0 和 false 等有效值不会被替换为空白。
6. 总结
在 React 中将 JSON 转换为 Excel,需要先整理数据,再将数据写入工作表,并下载生成的工作簿。扁平 JSON 数组可以直接导出,而嵌套数据需要明确的扁平化策略。通过按数据类型写入单元格,并统一收集字段,这两种工作流都能生成便于共享和进一步分析的 Excel 文件。
获取免费许可证
如果您需要去除生成文档中的评估提示或解除功能限制,请联系我们获取有效期 30 天的临时许可证。







