
设想一下:你的应用程序收到了一份 PDF 发票。你需要的并不是页面上的原始文本,而是发票编号、供应商名称和明细行 —— 一份你的 API 可以直接读取的结构化 JSON。这才是 PDF 转 JSON 真正要解决的核心问题。
与 CSV 或 XML 不同,PDF 文件并不具备内建的数据结构 —— 没有字段定义、没有行列概念、没有数据模式。能从中提取出什么样的 JSON,取决于文档实际包含的内容类型:是键值对形式的纯文本、行列排列的表格、可填写的表单字段,还是需要 OCR 才能识别的扫描图像。
本文将通过可运行的 C# 代码逐一覆盖上述四种场景,基于 Spire.PDF for .NET。我们将构建一个真正的发票转 JSON 转换器,处理合并单元格、缺少表头等常见的表格提取难题,并将所有逻辑封装为一个可复用的 PdfToJsonConverter 类,方便你直接集成到任何 .NET 项目中使用。
快速导航
- "PDF 转 JSON"的真正含义
- 安装 Spire.PDF for .NET
- 使用 C# 将 PDF 文本转换为 JSON
- 使用 C# 将 PDF 表格转换为 JSON
- 将 PDF 表单字段转换为 JSON
- 实战案例:发票 PDF 转 JSON
- 批量转换多个 PDF 为 JSON
- 构建 C# PDF 转 JSON 转换器
- 使用 C# 将 OCR 输出转换为 JSON
- 性能考量
- 常见问题
1. "PDF 转 JSON"的真正含义
PDF 并没有像 CSV 转 JSON 那样的"一键转换"能力。PDF 本身不含任何 JSON 结构。开发者真正要做的是:从 PDF 中提取内容,然后将这些内容组织成与自身业务场景匹配的 JSON 格式。
根据 PDF 类型和业务需求的不同,目标 JSON 通常属于以下三类之一。
原始文本 JSON
从每一页提取全部文本,封装为 JSON 输出。适用于搜索索引、RAG 流水线和文档归档等场景。
{
"sourceFile": "Contract.pdf",
"pages": [
{ "pageNumber": 1, "text": "服务协议\n甲方 Contoso Ltd 与..." }
]
}
键值对 JSON
许多 PDF 遵循 标签: 值 的格式 —— 如员工档案、注册表单、简单发票等。目标是将这些键值对解析为扁平的 JSON 对象:
{
"name": "John Smith",
"email": "该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。",
"department": "Engineering",
"employeeId": "EMP-2026-0142"
}
结构化业务 JSON
实际业务文档中的数据往往具有嵌套关系:一份发票可能同时包含表头信息、明细行、税费和付款条款,输出的 JSON 需要反映这种层次结构:
{
"invoiceNumber": "INV-2026-0042",
"vendor": "Contoso Ltd",
"date": "2026-06-15",
"lineItems": [
{ "description": "Widget A", "quantity": 150, "unitPrice": 24.50, "total": 3675.00 }
],
"subtotal": 3675.00,
"tax": 294.00,
"total": 3969.00
}
区分这三种格式非常重要。 当寻求"将 PDF 转换为 JSON"时,首先要想清楚应用程序到底需要哪种输出。本文后续内容将逐一展示如何使用 Spire.PDF 在 C# 中实现每种格式。
2. 安装 Spire.PDF for .NET
通过 NuGet 包管理器控制台安装:
Install-Package Spire.PDF
或在 .csproj 文件中添加:
<PackageReference Include="Spire.PDF" Version="*" />
在你的项目中引入以下命名空间:
using Spire.Pdf;
using Spire.Pdf.Texts;
using Spire.Pdf.Utilities;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;
using System.Text.Json;
using System.Text.Json.Serialization;
Spire.PDF 支持 .NET Framework、.NET Core 及 .NET 6/7/8/9+。
3. 使用 C# 将 PDF 文本转换为 JSON
先从最常见的需求开始:从 PDF 中提取文本,输出为 JSON。
从 PDF 中提取文本
using Spire.Pdf;
using Spire.Pdf.Texts;
using System.Collections.Generic;
using (PdfDocument pdf = new PdfDocument())
{
pdf.LoadFromFile("EmployeeRecord.pdf");
var pages = new List<Dictionary<string, string>>();
for (int i = 0; i < pdf.Pages.Count; i++)
{
PdfPageBase page = pdf.Pages[i];
PdfTextExtractOptions options = new PdfTextExtractOptions();
options.IsExtractAllText = true;
PdfTextExtractor extractor = new PdfTextExtractor(page);
string pageText = extractor.ExtractText(options);
pages.Add(new Dictionary<string, string>
{
{ "pageNumber", (i + 1).ToString() },
{ "text", pageText.Trim() }
});
}
}
将键值对解析为 JSON
如果你的 PDF 遵循 标签: 值 的格式,可将提取的文本解析为结构化字段:
using System.Text.Json;
using System.Text.Encodings.Web;
var parsedFields = new Dictionary<string, string>();
foreach (var page in pages)
{
string[] lines = page["text"].Split('\n');
foreach (string line in lines)
{
int colonIndex = line.IndexOf(':');
if (colonIndex < 0)
{
colonIndex = line.IndexOf(':');
}
if (colonIndex > 0)
{
string key = line[..colonIndex].Trim();
string value = line[(colonIndex + 1)..].Trim();
parsedFields[key] = value;
}
}
}
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
string jsonOutput = JsonSerializer.Serialize(parsedFields, jsonOptions);
File.WriteAllText("EmployeeRecord.json", jsonOutput);
关键 API 调用
PdfDocument.LoadFromFile()— 打开 PDF 文件PdfTextExtractor.ExtractText()— 从页面中提取文本内容PdfTextExtractOptions.IsExtractAllText— 保留空白字符和格式
输出示例
以下示例展示了从提取的员工档案中生成的结构化 JSON。
{
"name": "John Smith",
"email": "该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。",
"department": "Engineering",
"employeeId": "EMP-2026-0142",
"startDate": "2024-03-15"
}
以下截图展示了运行示例后实际生成的 JSON 文件。

这种方式适用于表单、档案以及具有一致键值布局的文档。对于非结构化文本,可以跳过解析步骤,直接序列化原始页面内容。
如需深入了解 PDF 文本提取,可参阅我们的专题指南:使用 Spire.PDF for .NET 在 C# 中从 PDF 提取文本。
4. 使用 C# 将 PDF 表格转换为 JSON
上一节介绍了如何从 PDF 中提取纯文本。这对于段落和简单档案来说效果不错,但很多商业文档的核心价值信息是组织在表格里的 —— 比如发票明细、销售报表和财务报表。为了让行与列、单元格之间的对应关系不丢失,表格数据需要先以不同于纯文本的方式提取,再转换为结构化 JSON。
为什么表格提取与文本提取不同
文本提取是按阅读顺序返回一段连续的字符流。即使表格在页面上看起来排列得整整齐齐,提取出来的文本也往往会丢失行列结构,很难判读哪些值原本属于同一行。
要保留表格布局,你需要一个专门的表格提取引擎。PdfTableExtractor 会分析页面布局、检测表格边界,并返回 PdfTable 对象,让你可以逐行逐列地遍历。与其得到一段难以解析的连续文本:
Widget A 150 $24.50 $3,675.00
而是生成结构化的 JSON:
{
"Product": "Widget A",
"Quantity": "150",
"Unit Price": "$24.50",
"Total": "$3,675.00"
}
以下示例演示了如何从 PDF 中提取表格并将其序列化为 JSON。
从 PDF 中提取表格
using Spire.Pdf;
using Spire.Pdf.Utilities;
using System.Collections.Generic;
using (PdfDocument pdf = new PdfDocument())
{
pdf.LoadFromFile("SalesReport.pdf");
PdfTableExtractor tableExtractor = new PdfTableExtractor(pdf);
var allTables = new List<List<List<string>>>();
for (int pageIndex = 0; pageIndex < pdf.Pages.Count; pageIndex++)
{
PdfTable[] tables = tableExtractor.ExtractTable(pageIndex);
if (tables != null && tables.Length > 0)
{
foreach (PdfTable table in tables)
{
int rowCount = table.GetRowCount();
int colCount = table.GetColumnCount();
var tableData = new List<List<string>>();
for (int row = 0; row < rowCount; row++)
{
var rowData = new List<string>();
for (int col = 0; col < colCount; col++)
{
rowData.Add(table.GetText(row, col).Trim());
}
tableData.Add(rowData);
}
allTables.Add(tableData);
}
}
}
}
将表格数据序列化为 JSON
var jsonTables = new List<object>();
foreach (var tableData in allTables)
{
if (tableData.Count < 2) continue;
var headers = tableData[0];
var rows = new List<Dictionary<string, string>>();
for (int i = 1; i < tableData.Count; i++)
{
var rowObj = new Dictionary<string, string>();
for (int j = 0; j < headers.Count && j < tableData[i].Count; j++)
{
rowObj[headers[j]] = tableData[i][j];
}
rows.Add(rowObj);
}
jsonTables.Add(new
{
tableIndex = allTables.IndexOf(tableData) + 1,
headers = headers,
data = rows
});
}
string tableJson = JsonSerializer.Serialize(new
{
sourceFile = "SalesReport.pdf",
tables = jsonTables
}, new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
File.WriteAllText("SalesReport_Tables.json", tableJson);
关键 API 调用
PdfTableExtractor(PdfDocument)— 初始化表格提取引擎PdfTableExtractor.ExtractTable(pageIndex)— 检测并提取页面中的表格PdfTable.GetRowCount()/GetColumnCount()— 返回表格的维度PdfTable.GetText(row, col)— 读取单元格内容
JSON 输出示例
生成的 JSON 通过将每一行按检测到的列标题组织为键值对,保留了原始表格结构。
{
"sourceFile": "SalesReport.pdf",
"tables": [
{
"tableIndex": 1,
"headers": ["Product", "Quantity", "Unit Price", "Total"],
"data": [
{ "Product": "Widget A", "Quantity": "150", "Unit Price": "$24.50", "Total": "$3,675.00" },
{ "Product": "Widget B", "Quantity": "80", "Unit Price": "$39.90", "Total": "$3,192.00" }
]
}
]
}
以下截图展示了运行示例后实际生成的 JSON 文件。

这种方式适用于发票、报表等表格结构清晰的 PDF。如果文档中包含合并单元格、缺少表头或跨页表格,则可能需要额外的后处理。
如需深入了解 PDF 表格提取,可参阅我们的专题指南:使用 Spire.PDF for .NET 在 C# 中提取 PDF 表格。
常见的表格提取问题
现实中的 PDF 表格往往不会那么规整。下面是你最常遇到的三个问题,以及对应的处理方式。
问题一:缺少表头
许多发票和报表中的表格没有明确的表头行,数据直接从第一行开始:
Apple 10 $2.99 $29.90
Orange 5 $1.50 $7.50
当第一行直接就是数据而没有表头时,你可以根据对文档结构的了解手动指定列名:
// 当 PDF 表格没有表头行时,手动定义列名
string[] defaultHeaders = { "Product", "Quantity", "UnitPrice", "Total" };
var rows = new List<Dictionary<string, string>>();
for (int i = 0; i < tableData.Count; i++) // 从 0 开始,而非 1
{
var rowObj = new Dictionary<string, string>();
for (int j = 0; j < defaultHeaders.Length && j < tableData[i].Count; j++)
{
rowObj[defaultHeaders[j]] = tableData[i][j];
}
rows.Add(rowObj);
}
问题二:合并单元格
财务报表中的表格经常使用合并单元格进行分组:
Quarter Revenue Expenses
Q1 $120,000 $95,000
$115,000 $88,000
Q2 $140,000 $102,000
提取器对合并单元格会返回空字符串。需要从上一个非空值向前填充:
// 用前一行的值填充合并单元格
for (int col = 0; col < headers.Count; col++)
{
string lastValue = "";
for (int row = 1; row < tableData.Count; row++)
{
if (col < tableData[row].Count && !string.IsNullOrWhiteSpace(tableData[row][col]))
{
lastValue = tableData[row][col];
}
else if (col < tableData[row].Count)
{
tableData[row][col] = lastValue;
}
}
}
问题三:跨页表格
企业报表中的单个表格常常跨越多页,且表头行在每一页上重复出现。处理方式是在序列化时对重复表头进行去重:
var combinedRows = new List<Dictionary<string, string>>();
string[] expectedHeaders = null;
for (int pageIndex = 0; pageIndex < pdf.Pages.Count; pageIndex++)
{
PdfTable[] tables = tableExtractor.ExtractTable(pageIndex);
if (tables == null) continue;
foreach (PdfTable table in tables)
{
for (int r = 0; r < table.GetRowCount(); r++)
{
var cells = new List<string>();
for (int c = 0; c < table.GetColumnCount(); c++)
{
cells.Add(table.GetText(r, c).Trim());
}
// 第一页的第一行作为表头
if (expectedHeaders == null && r == 0)
{
expectedHeaders = cells.ToArray();
continue;
}
// 跳过后续页面上重复的表头行
if (r == 0 && cells.SequenceEqual(expectedHeaders))
continue;
var rowDict = new Dictionary<string, string>();
for (int c = 0; c < expectedHeaders.Length && c < cells.Count; c++)
{
rowDict[expectedHeaders[c]] = cells[c];
}
combinedRows.Add(rowDict);
}
}
}
5. 将 PDF 表单字段转换为 JSON
与纯文本或表格不同,可填写的 PDF 表单本身就是以命名字段来存储数据的。申请表、调查问卷、注册表单等文档中,每个字段都有名称和对应的值,可以直接映射为 JSON 键值对 —— 这也让表单数据成为 PDF 中最容易序列化的内容类型之一。
读取并导出表单字段
using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Encodings.Web;
using (PdfDocument pdf = new PdfDocument())
{
pdf.LoadFromFile("E:\\IDEProjects\\OfficialArticles-CodeArts\\Samples\\RegistrationForm.pdf");
PdfFormWidget formWidget = pdf.Form as PdfFormWidget;
var formData = new Dictionary<string, object>();
if (formWidget != null)
{
for (int i = 0; i < formWidget.FieldsWidget.List.Count; i++)
{
PdfField field = formWidget.FieldsWidget.List[i] as PdfField;
if (field is PdfTextBoxFieldWidget textBox)
formData[textBox.Name] = textBox.Text;
else if (field is PdfCheckBoxWidgetFieldWidget checkBox)
formData[checkBox.Name] = checkBox.Checked;
else if (field is PdfRadioButtonListFieldWidget radioButton)
formData[radioButton.Name] = radioButton.Value;
else if (field is PdfComboBoxWidgetFieldWidget comboBox)
formData[comboBox.Name] = comboBox.SelectedValue;
else if (field is PdfListBoxWidgetFieldWidget listBox)
{
var selectedItems = new List<string>();
foreach (PdfListWidgetItem item in listBox.Values)
selectedItems.Add(item.Value);
formData[listBox.Name] = selectedItems;
}
}
}
var formOutput = new
{
sourceFile = "RegistrationForm.pdf",
fieldCount = formData.Count,
fields = formData
};
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
string json = JsonSerializer.Serialize(formOutput, jsonOptions);
File.WriteAllText("RegistrationForm_Data.json", json);
}
关键 API 调用
PdfFormWidget— 提供对文档交互式表单的访问PdfTextBoxFieldWidget.Text— 读取文本输入值PdfCheckBoxWidgetFieldWidget.Checked— 读取复选框状态PdfRadioButtonListFieldWidget.Value— 读取选中的单选按钮PdfComboBoxWidgetFieldWidget.SelectedValue— 读取下拉框选中项
输出示例
以下示例展示了提取的表单字段如何表示为结构化 JSON。
{
"sourceFile": "RegistrationForm.pdf",
"fieldCount": 6,
"fields": {
"FullName": "John Smith",
"Email": "该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。",
"Department": "Sales",
"AgreeTerms": true,
"SubscriptionPlan": "Enterprise",
"Skills": ["C#", "SQL", "Azure"]
}
}
以下截图展示了导出表单数据后实际生成的 JSON 文件。

这种方式适用于包含文本框、复选框、单选按钮和下拉列表等结构化字段的交互式 PDF 表单。由于每个字段已经具有唯一名称,提取出的数据可以直接序列化为 JSON,无需额外的解析步骤。
如需深入了解在 C# 中导入和导出 PDF 表单字段数据,可参阅我们的专题指南:使用 Spire.PDF for .NET 处理 PDF 表单字段。
6. 实战案例:发票 PDF 转 JSON
发票处理是 PDF 转 JSON 最常见的业务场景之一。本节不展示完整的解析器实现,而是演示如何将第 3 节的文本提取和第 4 节的表格提取组合起来,解决一个实际的业务问题。
目标 JSON 结构
在编写任何提取代码之前,首先要定义目标模式。对于一份典型的发票,JSON 输出可能如下所示:
{
"invoiceNumber": "INV-2026-0042",
"date": "2026-06-15",
"vendor": "Contoso Ltd",
"paymentTerms": "Net 30",
"lineItems": [
{ "description": "Widget A", "quantity": 150, "unitPrice": 24.50, "total": 3675.00 },
{ "description": "Widget B", "quantity": 80, "unitPrice": 39.90, "total": 3192.00 }
],
"subtotal": 8367.00,
"tax": 669.36,
"total": 9036.36
}
提取模式
使用文本提取(第 3 节)通过正则表达式解析表头字段,使用表格提取(第 4 节)获取明细行:
// 使用正则表达式从提取的文本中解析表头字段
invoice["invoiceNumber"] = Regex.Match(fullText, @"Invoice Number:\s*(\S+)").Groups[1].Value;
invoice["date"] = Regex.Match(fullText, @"Date:\s*(\S+)").Groups[1].Value;
invoice["vendor"] = Regex.Match(fullText, @"Vendor:\s*(.+)").Groups[1].Value;
// 从表格数据中提取明细行(第 4 节的模式)
for (int r = 1; r < table.GetRowCount(); r++)
{
lineItems.Add(new
{
description = table.GetText(r, 0).Trim(),
quantity = int.Parse(table.GetText(r, 1).Trim()),
unitPrice = ParseCurrency(table.GetText(r, 2)),
total = ParseCurrency(table.GetText(r, 3))
});
}
该实现将第 3 节介绍的文本提取与第 4 节介绍的表格提取相结合。正则表达式仅用于简单的字段匹配 —— 核心的 PDF 处理完全依赖 Spire.PDF API。
处理不同的发票布局
实际生产环境中,你通常不会只面对一种发票格式:
- 固定模板 + 正则表达式 — 当你掌控数据来源或处理已知供应商的发票时适用
- 模板匹配 — 维护一组正则表达式模式,每个供应商对应一套
- AI 辅助提取 — 对于未知或高度可变的布局,将 OCR 输出与大语言模型相结合
基于正则表达式的解析对于已知格式来说既快速又可靠。要构建可用于生产环境的实现,你可以扩展第 8 节中的 PdfToJsonConverter 类,创建一个专用的发票解析器,复用已有的提取模式。
7. 批量转换多个 PDF 为 JSON
生产工作流通常需要一次性处理成百上千个 PDF。以下批量处理器能够妥善处理错误并记录日志:
using Spire.Pdf;
using Spire.Pdf.Texts;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
string inputDir = @"C:\PDFs\Invoices";
string outputDir = @"C:\Output\JSON";
Directory.CreateDirectory(outputDir);
string[] pdfFiles = Directory.GetFiles(inputDir, "*.pdf");
var results = new List<object>();
foreach (string pdfPath in pdfFiles)
{
string fileName = Path.GetFileNameWithoutExtension(pdfPath);
string outputPath = Path.Combine(outputDir, $"{fileName}.json");
try
{
using (PdfDocument pdf = new PdfDocument())
{
pdf.LoadFromFile(pdfPath);
var pageTexts = new List<string>();
for (int i = 0; i < pdf.Pages.Count; i++)
{
var extractor = new PdfTextExtractor(pdf.Pages[i]);
var options = new PdfTextExtractOptions { IsExtractAllText = true };
pageTexts.Add(extractor.ExtractText(options).Trim());
}
var doc = new
{
sourceFile = Path.GetFileName(pdfPath),
pageCount = pdf.Pages.Count,
processedAt = DateTime.UtcNow,
content = pageTexts
};
File.WriteAllText(outputPath, JsonSerializer.Serialize(doc,
new JsonSerializerOptions { WriteIndented = true }));
results.Add(new { file = fileName, status = "success" });
}
}
catch (Exception ex)
{
results.Add(new { file = fileName, status = "error", error = ex.Message });
}
}
File.WriteAllText(Path.Combine(outputDir, "_log.json"),
JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true }));
如果你的批处理对象是发票,可将上述纯文本提取替换为第 6 节中的发票 JSON 提取模式;对于通用转换,可使用第 8 节中的 PdfToJsonConverter 类。
8. 构建 C# PDF 转 JSON 转换器
在生产应用中,建议将所有提取逻辑封装到一个统一的类中。下面的 PdfToJsonConverter 将文本、表格和表单字段提取整合为一个可复用的 PDF 转 JSON 转换器:
using Spire.Pdf;
using Spire.Pdf.Texts;
using Spire.Pdf.Utilities;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
public class PdfToJsonConverter
{
private readonly JsonSerializerOptions _jsonOptions = new()
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};
public string ConvertToJson(string pdfPath)
{
using (PdfDocument pdf = new PdfDocument())
{
pdf.LoadFromFile(pdfPath);
var result = new
{
sourceFile = Path.GetFileName(pdfPath),
processedAt = DateTime.UtcNow,
text = ExtractText(pdf),
tables = ExtractTables(pdf),
formFields = ExtractFormFields(pdf)
};
return JsonSerializer.Serialize(result, _jsonOptions);
}
}
public void ConvertAndSave(string pdfPath, string outputPath)
{
File.WriteAllText(outputPath, ConvertToJson(pdfPath));
}
// 复用第 3 节的文本提取技术(PdfTextExtractor + PdfTextExtractOptions)
private List<PageText> ExtractText(PdfDocument pdf) { return new List<PageText>(); }
// 复用第 4 节的表格提取技术(PdfTableExtractor + ExtractTable)
private List<TableData> ExtractTables(PdfDocument pdf) { return new List<TableData>(); }
// 复用第 5 节的表单字段提取技术(PdfFormWidget + 字段类型判断)
private Dictionary<string, object> ExtractFormFields(PdfDocument pdf) { return new Dictionary<string, object>(); }
}
public class PageText
{
public int PageNumber { get; set; }
public string Text { get; set; }
}
public class TableData
{
public int PageNumber { get; set; }
public int RowCount { get; set; }
public List<List<string>> Rows { get; set; }
}
使用方式
var converter = new PdfToJsonConverter();
// 处理单个文件
converter.ConvertAndSave("InvoiceReport.pdf", "InvoiceReport.json");
// 在 ASP.NET 控制器中使用
[HttpPost("api/pdf-to-json")]
public IActionResult ConvertPdf(IFormFile file)
{
var tempPath = Path.GetTempFileName();
file.CopyTo(new FileStream(tempPath, FileMode.Create));
var converter = new PdfToJsonConverter();
string json = converter.ConvertToJson(tempPath);
return Content(json, "application/json");
}
辅助方法(ExtractText、ExtractTables、ExtractFormFields)分别复用了第 3 节到第 5 节介绍的提取技术。完整实现请参阅相应章节。
生产环境最佳实践
将 PDF 转 JSON 功能集成到生产系统时,请注意以下几点:
- 首先定义 JSON 模式。 在编写提取代码之前,先将每个 PDF 元素映射到目标字段。
- 验证提取数据。 货币字符串、日期和 ID 应在序列化之前进行解析验证。
- 处理缺失值。 使用
JsonIgnoreCondition.WhenWritingNull在输出中忽略空值字段。 - 包含元数据。 始终记录源文件名、页码和提取时间戳,以便审计。
- 清理文本杂质。 对提取的字符串进行空白修剪、换行符标准化和编码问题处理。
9. 使用 C# 将 OCR 输出转换为 JSON
扫描版 PDF 包含的是图像而非可选择的文本,因此必须先通过 OCR 引擎处理才能转换为 JSON。Spire.PDF 负责 PDF 渲染和页面处理,文本识别则应由 OCR 方案(如 Tesseract 或 Azure AI Vision)来完成。
完整操作指南请参阅 如何在 C# 中从扫描件 PDF 提取文本。
OCR 返回识别文本后,你可以使用本文前述的相同技术进行解析。
将 OCR 文本解析为 JSON
string recognizedText = ocrEngine.Recognize(imagePath);
// 使用前面示例中展示的相同辅助方法解析识别文本
var parsedData = ParseRecognizedText(recognizedText);
string json = JsonSerializer.Serialize(parsedData, new JsonSerializerOptions
{
WriteIndented = true
});
最佳实践
- 将文档扫描分辨率设置为 300 DPI 或更高,以获得更好的 OCR 准确性。
- 在序列化之前验证关键字段,如发票编号、日期和货币值。
- 复用本文前述的解析模式,构建一致的 JSON 结构。
10. 性能考量
对于只有几页的文档,PDF 转 JSON 的转换通常不成问题。但到了生产环境,你需要面对的是成百上千个文件,每个文件可能多达数百页。下面是你将真实遇到的性能挑战。
大型 PDF(100 页以上)
避免在序列化之前将所有页面文本加载到 List<string> 中。改为逐页处理并增量写出:
using (var stream = File.Create("output.json"))
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
{
writer.WriteStartObject();
writer.WriteString("sourceFile", Path.GetFileName(pdfPath));
writer.WriteStartArray("pages");
for (int i = 0; i < pdf.Pages.Count; i++)
{
var extractor = new PdfTextExtractor(pdf.Pages[i]);
var options = new PdfTextExtractOptions { IsExtractAllText = true };
string text = extractor.ExtractText(options).Trim();
writer.WriteStartObject();
writer.WriteNumber("pageNumber", i + 1);
writer.WriteString("text", text);
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
Utf8JsonWriter 直接写入流,而不是先在内存中构建字符串。对于一个 500 页的文档,与 JsonSerializer.Serialize() 相比,这种方式可以将峰值内存占用降低 60%–70%。
内存使用
PdfDocument 会在内存中保存解析后的页面树、字体和图像引用。有两条规则:
- 始终用
using包裹PdfDocument— 它在释放时会清理非托管资源 - 一次只处理一个文档 — 除非你有足够的 RAM,否则不要同时保持多个
PdfDocument实例打开
对于处理 1000 个以上文件的批处理任务,在循环内部使用 using 模式可确保每个文档在加载下一个之前被完全释放。
并行处理
批量转换属于 CPU 密集型任务,非常适合并行化:
var pdfFiles = Directory.GetFiles(inputDir, "*.pdf");
Parallel.ForEach(pdfFiles,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
pdfPath =>
{
string outputPath = Path.Combine(outputDir,
Path.GetFileNameWithoutExtension(pdfPath) + ".json");
var converter = new PdfToJsonConverter();
converter.ConvertAndSave(pdfPath, outputPath);
});
每个线程创建各自的 PdfToJsonConverter 和 PdfDocument 实例。PdfDocument 不是线程安全的 —— 切勿在线程之间共享单个实例。
何时使用流式 JSON
在以下情况下,应使用 Utf8JsonWriter 而非 JsonSerializer.Serialize():
- 输出的 JSON 超过 50 MB
- 正在处理 200 页以上的 PDF
- 在内存受限的环境(如 512 MB 限制的容器)中运行
对于较小的文档,JsonSerializer 更简单,且内存差异可忽略不计。
11. 常见问题
能否在 C# 中免费将 PDF 转换为 JSON?
Spire.PDF for .NET 提供免费的评估版本,但有页数限制。对于生产使用,你可以申请 30 天免费许可证或购买商业许可证。System.Text.Json 序列化器是 .NET 内置的,完全免费。
扫描版 PDF 可以转换为 JSON 吗?
可以,但需要使用外部 OCR 引擎。Spire.PDF 通过 SaveAsImage() 将 PDF 页面渲染为图像,然后你可以将其传递给 Tesseract、Azure Computer Vision 或 Amazon Textract 进行文本识别。识别后的文本解析和序列化为 JSON 的流程与常规 PDF 相同。集成模式请参阅第 9 节。
可以自动将 PDF 表格转换为 JSON 吗?
可以。PdfTableExtractor 会自动检测每一页上的表格结构,无需手动配置。它既能处理结构规范的表格(在 Word 或 Excel 中创建的),也能处理视觉表格(文本对齐排列为行列形式)。对于跨页表格或无表头表格,请参阅第 4 节中的处理模式。
能否批量将多个 PDF 转换为 JSON?
可以。使用 Directory.GetFiles() 遍历目录,用 Spire.PDF 提取 API 处理每个 PDF,并保存为独立的 JSON 文件。记得加入错误处理,确保单个文件失败不会中断整个批处理。完整示例请参阅第 7 节。
如何在 C# 中转换大型 PDF 文件为 JSON?
逐页处理 PDF,而不是一次性将所有内容加载到内存中。对于非常大的文件(100 页以上),使用 Utf8JsonWriter 以流的方式增量写出 JSON,避免在内存中构建完整的输出。流式 JSON 模式和并行处理方法请参阅第 10 节。
能否通过 API 将 PDF 转换为 JSON?
可以。你可以将本文中的 PdfToJsonConverter 类封装到 ASP.NET Web API 端点中。接受 PDF 上传,运行提取,返回 JSON 响应。Spire.PDF 适用于任何 .NET 宿主环境 —— ASP.NET Core、Azure Functions、AWS Lambda 或自托管的控制台应用均可。ASP.NET 控制器示例请参阅第 8 节。
总结
PDF 转 JSON 并不是一个单一的操作。根据文档类型的不同,你实际上在解决三种不同的问题:将原始文本封装为 JSON 输出、将键值对解析为扁平对象,或通过文本与表格提取构建结构化的业务 JSON。
本文覆盖了以上全部三种场景,以及那些简单实现无法应对的复杂情况:无表头表格、合并单元格、跨页表格、可填写表单字段、多样化的发票布局、批量处理、大型文档的内存管理,以及 OCR 集成边界。
PdfToJsonConverter 类是一个可以按你的文档类型灵活定制的起点。第 6 节展示的发票提取模式则演示了如何将这些技术组合起来处理真实的业务文档。两者都基于 Spire.PDF for .NET,它在本地完成所有 PDF 读取工作,无需任何外部依赖。
开始使用:
- 通过 NuGet 安装:
Install-Package Spire.PDF - 申请 30 天免费许可证,无页数限制地评估
- 浏览 Spire.PDF 文档,探索更多提取场景







