Java中的TOON格式介绍:优化大语言模型的数据传输
Java 中的 TOON 格式简介
1. 概述
JSON 是在系统间交换结构化数据的标准格式。然而,当我们向大语言模型 (LLM) 发送 JSON 时,很大一部分 Token 预算会浪费在语法上(如大括号、方括号、引号和重复的键名),而不是实际数据本身。
TOON (Token-Oriented Object Notation) 是一种紧凑且易于阅读的格式。它使用与 JSON 相同的数据模型进行编码,但显著减少了所需的 Token 数量。它摒弃了 JSON 中繁琐的标点符号语法,采用缩进表示层级;对于统一的集合,它采用表格布局,即仅在表头声明一次字段名称,后续按行流式传输数据值。
在本教程中,我们将概览现有的 Java TOON 库,使用 json-io 演示 TOON 的序列化与反序列化,对比不同格式的 Token 占用情况,并讨论 TOON 的最佳适用场景。
2. 什么是 TOON?
TOON 对与 JSON 相同的基元、对象和数组进行编码,但语法存在差异:
- 对象:使用 键: 值 对,通过换行和缩进分隔,而不是使用大括号。
- 数组:使用 [N]: 这种带有长度前缀的列表,而不是方括号。
- 表格数组:在首行声明字段名称,后续行以类 CSV 格式列出值。
- 引号:需求极小——仅当字符串包含结构字符时才需要引号。
以下是相同数据在 JSON 和 TOON 中的对比:
JSON:
TOON(表格格式):
表格格式在表头中声明了一次 name、age 和 department。后续每一行仅包含值。随着行数增加,节省的效果会不断累积,因为 JSON 需要在每个对象中重复所有键名。
对于单个对象,TOON 使用类似 YAML 的键值布局:
完整规范请参考 toonformat.dev。
3. Java 的 TOON 库
TOON 生态系统 包含了超过 25 种语言的实现。目前 Java 有两个可用库:
两个库均支持 TOON 的编码与解码,包括表格数组。JToon 是官方社区 Java 实现,专为 TOON 构建并集成了 Jackson。json-io 是一个成熟的 JSON 序列化库,在 JSON 之外增加了 TOON 作为输出格式。
我们可以在 Maven Central 上找到最新版的 json-io。本教程的示例将使用 json-io,因为它支持 Java 8 到 24,适用范围最广:
4. 将 Java 对象写入 TOON
4.1. 单个对象
我们可以使用 JsonIo.toToon() 将任何 Java 对象转换为 TOON:
输出结果:
第二个参数接收一个 WriteOptions 实例用于控制输出,传入 null 则使用默认值。
4.2. 集合:表格格式
表格格式是 TOON 发挥最大优势的地方。当我们序列化一个共享相同字段的对象列表时,json-io 会自动使用紧凑的“每行一个对象”布局:
输出结果:
[3] 声明了数组长度。{name,age,department,salary} 表头定义了列名。每一行仅包含以逗号分隔的值。键名仅出现一次。
如果需要展开的列表格式(每行一个键值对),可以使用构建器模式启用 prettyPrint:
这将产生详细格式:
默认的表格布局在与 LLM 交互时更受欢迎,因为它最大限度地减少了 Token。展开的 prettyPrint 形式会在每个对象上重复每个键名,因此其 Token 占用量与 JSON 大致相同,节省效果会消失。
4.3. 嵌套结构
TOON 通过缩进自然地处理嵌套。例如,一个公司包含部门(存储在 LinkedHashMap 中),每个部门包含成员列表:
json-io 会在文档的每一层应用此优化:任何元素仅包含标量字段的数组都会被自动渲染为表格格式。这种在单个文档中混合键值对和表格行的能力是 CSV 无法实现的。
5. 从 TOON 读取回 Java
解析 TOON 是写入的逆过程。我们可以反序列化为具体的 Java 对象:
对于泛型集合类型,我们使用 TypeHolder 来规避类型擦除:
当没有可用的 Java 类时(例如在中间件或日志处理中),可以解析为 Map:
6. Token 效率:衡量差异
TOON 的初衷是减少发送给 LLM 的结构化数据的 Token 消耗。我们使用 GPT 使用的相同 tokenizer(o200k_base 编码)进行测试:
随着集合规模的增加,节省的效果愈发明显。对于 20 个项目(4 个字段),我们节省了 44% 的 Token。
6.1. TOON 与 CSV 相比如何?
对于纯扁平数据,CSV 的 Token 占用略少于 TOON(约少 6%),因为 TOON 包含表头和缩进。但 CSV 无法表示嵌套数据。当试图将嵌套的公司-部门结构编码为 CSV 时,必须进行去规范化处理,导致数据重复,反而比 TOON 和 JSON 占用了更多 Token。
TOON 处于一个实用的中间地带:它既能在扁平数据上保持接近 CSV 的高效,又能处理现实世界 API 产生的嵌套和混合结构。
在 Agent 和工具调用工作流中,结构化数据往往占据了输入 Token 的大部分。减少 30-40% 的 Token 意味着更低的 API 成本以及更有效的上下文窗口。Java 应用只需将 JsonIo.toJson() 切换为 JsonIo.toToon() 即可实现。
7. Spring AI 集成
对于使用 Spring AI 的项目,json-io 提供了一个独立的 starter,可自动将工具调用结果转换为 TOON。添加依赖后,只需标注工具方法:
当 Spring AI 调用此工具并将结果传回 LLM 时,转换器会自动将员工列表序列化为 TOON 的表格格式。LLM 将以更少的 Token 接收数据,且无需更改任何业务逻辑。
8. 何时使用 TOON
在以下场景使用 TOON:
- 向 LLM 发送结构化数据(工具结果、RAG 有效载荷、数据分析上下文)。
- 处理统一对象的集合(表格格式的强项)。
- Token 成本或上下文窗口受限。
- 数据具有扁平与嵌套混合的结构。
坚持使用 JSON 的场景:
- 服务间通信(REST API、消息队列),因为消费者默认预期为 JSON。
- 数据消费者不是 LLM。
- 需要架构验证(JSON Schema)。
- 数据完全扁平且无嵌套(此时 CSV 可能更高效)。
JSON 的优势在于其庞大的生态系统,几乎所有工具都支持它。但在需要 Token 效率的场景(LLM 工具结果、RAG、Agent 工作流)中,TOON 在不损失表达能力的前提下提供了可衡量的节省。## 9. 总结
在本文中,我们调研了目前可用的 Java TOON 库:JToon 和 json-io。我们使用 json-io 将 Java 对象序列化为 TOON 格式,利用 OpenAI 的 BPE 分词器衡量了其相比 JSON 的 Token 节省效果,并将 TOON 集成到了 Spring AI 工具调用中。对于包含同构对象的集合,TOON 的表格格式比简洁的 JSON 减少了 30-44% 的 Token 占用。它在处理扁平数据时达到了接近 CSV 的效率,同时又能处理 CSV 无法表示的嵌套结构。
本文中的完整代码示例可在 GitHub 上获取。