从 JSON 到 Word:用 Python 把结构化数据渲染成可读文档

JSON 是机器之间交换数据的通用格式,Word 文档则是给人阅读的载体。两者之间的转换本质上不是"格式转换",而是一次结构映射 :JSON 的键值对、数组、嵌套对象,需要被翻译成段落、表格、标题等人类可读的文档组件。本文分享使用免费库 Free Spire.Doc for Python 完成 JSON 转 Word 的实现思路与代码示例,适合学习、轻量自动化场景。

环境准备

Free Spire.Doc for Python 是一个独立的 Word 文档处理库,不需要安装 Microsoft Word 即可创建和编辑 DOCX 文件。免费版通过 pip 安装:

bash 复制代码
pip install Spire.Doc.Free

需要提前了解免费版的容量边界:单文档最多 500 个段落和 25 个表格 ,超出部分会被截断。对于日常的报告生成、API 数据快照、中小规模台账来说够用;如果单次输出会稳定超过这个量级,就需要在数据分片或换用其他方案之间做选择。

核心层级关系是理解后续所有代码的基础:

复制代码
Document → Section → Paragraph → TextRange
                  ↘ Table → Row → Cell → Paragraph

Document 是文档根对象,Section 承载页面级设置,Paragraph 是文本容器,TextRange 是段落中一段连续文本的格式控制入口。

映射原则:JSON 结构决定 Word 形态

同一个 JSON,可以渲染出完全不同的文档。先确定映射规则,再写代码,比边写边调更可控。常见的对应关系如下:

JSON 结构 Word 元素 典型场景
扁平对象的键值对 段落(键:值) 配置说明、个人卡片、API 快照
对象数组 表格(键→列标题,对象→行) 员工列表、订单、库存
嵌套对象 标题 + 缩进段落(递归) 结构化报告、层级摘要
图片路径字段 内联图片 Logo、头像、图表快照

下面的代码按"扁平 → 数组 → 嵌套"的顺序递进,每个场景给出可直接运行的片段。

场景一:扁平对象 → 键值段落

这是最简单的映射:每个键值对生成一个段落。适合结构固定、字段不多的数据。

python 复制代码
import json
from spire.doc import Document, FileFormat, HorizontalAlignment
from spire.doc.common import *

json_str = '{"项目": "数据管道", "负责人": "张伟", "状态": "进行中", "启动日期": "2025-03-01"}'

data = json.loads(json_str)


doc = Document()

section = doc.AddSection()


title = section.AddParagraph()

title.AppendText("项目信息").CharacterFormat.Bold = True

title.Format.HorizontalAlignment = HorizontalAlignment.Center


for key, value in data.items():

para = section.AddParagraph()

para.AppendText(f"{key}:{value}").CharacterFormat.FontSize = 12

para.Format.AfterSpacing = 6

`doc.SaveToFile(`"flat_object.docx"`, FileFormat.Docx)`
`
doc.Close()`
`
`

AppendText 返回的是 TextRange 对象,字体、字号、加粗等字符级格式都通过它来设置;段落级间距和对齐则通过 paragraph.Format。这套格式控制方式,在后续的表格单元格里同样适用。

场景二:对象数组 → Word 表格

对象数组对应表格的一行,列标题从记录中提取。但不要假设所有记录键完全一致------真实 API 返回的可选字段经常缺失,直接用 record[key] 会让整批转换因为一条记录而中断。

更稳健的做法是先扫描全部记录,收集键的并集作为列标题:

python 复制代码
import json
from spire.doc import (
    Document, FileFormat, HorizontalAlignment, VerticalAlignment,
    TableRowHeightType, Color
)
from spire.doc.common import *

records = [

{"产品": "笔记本电脑", "单价": 6999, "库存": 45},

{"产品": "无线鼠标", "单价": 199, "库存": 200},

{"产品": "机械键盘", "单价": 549, "库存": 120},

]


# 动态收集列标题,兼容键不完全一致的记录

all_keys = []

for record in records:

for key in record:

if key not in all_keys:

all_keys.append(key)


doc = Document()

section = doc.AddSection()


table = section.AddTable(True)

table.ResetCells(len(records) + 1, len(all_keys))


# 表头

header_row = table.Rows[0]

header_row.IsHeader = True

header_row.Height = 24

header_row.HeightType = TableRowHeightType.Exactly


for col, key in enumerate(all_keys):

cell = header_row.Cells[col]

cell.CellFormat.BackColor = Color.get_Gray()

cell.CellFormat.VerticalAlignment = VerticalAlignment.Middle

p = cell.AddParagraph()

p.Format.HorizontalAlignment = HorizontalAlignment.Center

tr = p.AppendText(key)

tr.CharacterFormat.Bold = True

tr.CharacterFormat.FontSize = 11


# 数据行

for row_idx, record in enumerate(records, start=1):

row = table.Rows[row_idx]

row.Height = 22

row.HeightType = TableRowHeightType.Exactly

for col, key in enumerate(all_keys):

cell = row.Cells[col]

cell.CellFormat.VerticalAlignment = VerticalAlignment.Middle

p = cell.AddParagraph()

p.Format.HorizontalAlignment = HorizontalAlignment.Center

p.AppendText(str(record.get(key, "---")))

`doc.SaveToFile(`"array_table.docx"`, FileFormat.Docx)`
`
doc.Close()`
`
`

两个关键点:

  • ResetCells 的行数是 len(records) + 1,不是 len(records)。表头也占一行。
  • record.get(key, "---") 而不是下标访问。占位符保持列数统一,也避免单条记录缺字段导致整个脚本崩溃。

场景三:嵌套对象 → 递归标题结构

嵌套 JSON 表达的是层级关系,不能塞进二维表格。用递归遍历,用一个 level 参数控制缩进和字号,形成视觉层次:

python 复制代码
from spire.doc import Document, FileFormat
from spire.doc.common import *

def add_nested(section, obj, level=0):

if isinstance(obj, dict):

for key, value in obj.items():

if isinstance(value, (dict, list)):

heading = section.AddParagraph()

tr = heading.AppendText(str(key))

tr.CharacterFormat.Bold = True

tr.CharacterFormat.FontSize = max(14 - level * 2, 10)

heading.Format.SetLeftIndent(level * 18)

add_nested(section, value, level + 1)

else:

para = section.AddParagraph()

para.Format.SetLeftIndent(level * 18)

tr = para.AppendText(f"{key}:{value}")

tr.CharacterFormat.FontSize = max(12 - level, 10)

elif isinstance(obj, list):

for item in obj:

add_nested(section, item, level)


nested = {

"报告": {

"标题": "季度运营摘要",

"周期": "2025 Q2",

"指标": {"营收": "580万", "同比": "+12%"},

"备注": "所有区域均实现正增长"

}

}

`doc = Document()`
`
section = doc.AddSection()`
`
add_nested(section, nested)`
`
doc.SaveToFile(`"nested_report.docx"`, FileFormat.Docx)`
`
doc.Close()`
`
`

递归方法的好处是对深度不敏感------无论嵌套两层还是五层,逻辑不变。需要注意免费版的 500 段落上限:深层嵌套或超大数据集会快速消耗段落配额,在生产中应当根据实际输出规模做预判。

补充:JSON 中的图片字段

如果 JSON 里包含图片路径(如 "logo": "images/logo.png"),可以在对应位置用 AppendPicture 插入,并通过 Width / Height 控制显示尺寸(单位是磅):

python 复制代码
from spire.doc import Paragraph
`para = section.AddParagraph()`
`
pic = para.AppendPicture(`"images/logo.png"`)`
`
pic.Width = `80
`
pic.Height = `50
`
`

如果图片出现在表格单元格中,把 AppendPicture 挂在 cell.AddParagraph() 返回的段落上即可,与普通文本段落的使用方式一致。

结语

从 JSON 到 Word 的核心不在于库本身,而在于先想清楚数据的哪一层结构应该对应文档的哪一种元素 :键值对用段落,记录集合用表格,层级关系用递归标题。映射规则确定之后,Free Spire.Doc 提供的 Document → Section → Paragraph / Table 对象模型足够直接地把规则落到 DOCX 文件里。