condense-json:JSON 重复数据压缩库 · 干货攻略

  • 链接: https://simonwillison.net/2026/Aug/2/condense-json
  • 分类: x-tips
  • 来源: X @simonw
  • 作者: Jay
  • 更新: 2026-08-09
  • 仓库: simonw/condense-json

这是什么

condense-json 是 Simon Willison 开发的一个 Python 库(仓库 simonw/condense-json),通过标记引用机制压缩 JSON 内部重复数据。它不改变 JSON 的语义,而是将重复出现的字符串片段、字典或列表结构替换为紧凑的引用标记,从而减小序列化后的体积。解压缩时只需同一个 replacements 映射即可还原。

核心函数:

from condense_json import condense_json, uncondense_json

condensed = condense_json(obj, replacements)
restored = uncondense_json(condensed, replacements)
assert restored == original  # 结构性相等

当前有两个版本: - v1.0(2026-08-02):初始稳定版,Simon 自述「已经历 18 个月打磨」 - v1.1(2026-08-03):集成进 LLM 时发现的新功能——支持字典/列表作为 replacement 值(结构替换),以及 merge reference(基对象 + patch)


为什么值得关注

LLM 应用有一个很实在的工程痛点:日志存储成本高。每一次对话的请求/响应里,JSON 里往往包含大量重复内容——系统提示词、工具定义、工具调用 schema、同一个错误消息反复出现。SQLite 日志文件很快就膨胀到几十 MB 甚至 GB 级别。

condense-json 正是为这个问题设计的。它不追求通用的压缩算法,而是利用一个前提:调用方往往已经持有(或可以构造)一份重复数据的字典(即 replacements 映射)。有了这个字典,重复内容就可以被替换为指向它的引用标记,大幅减少存储体积。

这个库已经集成进 Simon 自家的 LLM CLI 工具(见 PR #1586),是经过生产验证的真实用例,而非概念原型。


核验过程

读过的官方来源:

  1. Simon Willison 博客 — condense-json 1.0(2026-08-02) - 确认 v1.0 发布,库龄约 18 个月,用于 LLM SQLite 日志 - 提供了基本示例({"$r": [...]} 语法)

  2. Simon Willison 博客 — condense-json 1.1(2026-08-03) - 确认新增结构替换(dict/list 作为 replacement 值)和 merge reference - 确认 Hypothesis round-trip 测试已添加

  3. GitHub releases 页 - 确认两个版本发布日期(Aug 2 和 Aug 3 2026) - 确认三个变更点:结构替换、merge reference、Hypothesis 测试

  4. GitHub README(完整文档) - 确认三种引用形式的技术细节

  5. Hypothesis 官方文档 — round-trip testing 章节 - 确认 round-trip 是 Hypothesis 官方推荐的经典属性测试模式

交叉验证: - 通过 Tavily 搜索「condense-json」第三方报道(Neura Market、Glonce)——两家描述与官方博客一致,未发现冲突信息 - 原帖「存储节省具体数字」说法:官方博客和第三方报道均未给出具体百分比或字节数,该说法无法核验,攻略中不引用

原帖说法校正: - 原帖用「替换对象机制」描述技术原理——不够精确。实际是标记引用而非原地替换对象,有三种不同引用形式(见下文),攻略正文以此为准。


上手步骤

安装

pip install condense-json

基础用法:字符串片段去重

from condense_json import condense_json, uncondense_json

input_json = {
    "foo": {
        "bar": {
            "string": "This is a string with foxes in it",
            "nested": {
                "more": ["Here is a string", "another with foxes in it too"]
            }
        }
    }
}

replacements = {"1": "with foxes in it"}

condensed = condense_json(input_json, replacements)
print(condensed)
# {
#   "foo": {
#     "bar": {
#       "string": {"$r": ["This is a string ", {"$": "1"}]},
#       "nested": {
#         "more": [
#           "Here is a string",
#           {"$r": ["another ", {"$": "1"}, " too"]}
#         ]
#       }
#     }
#   }
# }

restored = uncondense_json(condensed, replacements)
assert restored == input_json

进阶:结构替换(dict / list 作为 replacement 值)

v1.1 支持用 dict 或 list 作为 replacement 值,匹配时对整个子树做引用:

schema = {
    "type": "object",
    "properties": {"name": {"type": "string"}},
    "required": ["name"],
}
response = {
    "output": {"name": "Cleo"},
    "format": {
        # 与 schema 结构相同,但 key 顺序不同,仍然匹配
        "schema": {
            "required": ["name"],
            "type": "object",
            "properties": {"name": {"type": "string"}},
        }
    },
}

condensed = condense_json(response, {"s": schema})
# {'output': {'name': 'Cleo'}, 'format': {'schema': {'$': 's'}}}
assert uncondense_json(condensed, {"s": schema}) == response

进阶:Merge Reference(近匹配字典存储为基对象 + patch)

v1.1 新增:当某个字典与 replacement 字典接近但不完全相同时,condense-json 会把它存为「基对象 + 差量 patch」的形式:

# 输出中的 merge reference 格式:
{"$": {"m": base_id, "u": {...}, "d": [...]}}
# m=base_id, u=需要更新的键值, d=需要删除的键

uncondense_json() 收到后会执行合并还原。

在 LLM 日志场景中使用

Simon Willison 在 LLM CLI 中的集成方式(PR #1586)是:将请求中的 tool definitions 和 response 中的重复 schema 提取为 replacements,然后对整个 JSON 日志做压缩。这是一个可复用的模式:

# 伪代码示例
tool_defs = response.get("tool_calls", [])
if tool_defs:
    # 把 tool schema 注册为 replacements
    replacements = {f"tool_{i}": tool["function"] for i, tool in enumerate(tool_defs)}
    # 压缩整个响应日志
    condensed = condense_json(response, replacements)

坑与适用边界

1. replacements 对象需要调用方自行维护 condense-json 不从 JSON 内部自动提取 replacements——你必须自己构造。这在 tool_calls / schema 已知的情况下很自然,但如果要压缩任意 JSON,则需要额外的预处理步骤。

2. 结构性恢复,而非字节级恢复 uncondense_json(condense_json(obj, r), r) == obj 成立(这是 Hypothesis 属性测试保证的),但恢复后 key 的顺序会变成 replacement 的顺序。如果业务依赖 JSON key 顺序,压缩后需要额外注意。

3. 无具体节省数字 原帖提到「省存储」但官方和第三方报道均未给出任何具体百分比或字节数——这是真实效果,但数字是未核验的原帖主张,本次攻略不引用。实际收益取决于 JSON 中重复内容的比例。

4. 非字符串标量(数字、布尔)不参与替换 replacements 中值为数字(如 42)或布尔值的条目会被忽略,不会产生引用标记。

5. 严格校验 uncondense_json 在遇到格式错误的压缩数据时会抛出 UncondenseErrorValueError 的子类),而不是静默产生错误数据。这意味着调用方需要用 try/except 包装。

适用场景: - LLM 推理日志、SQLite JSON 列、API 响应缓存 - 工具调用 schema 重复出现多次的 agent 日志 - 任意「有一份字典 + 大量 JSON 引用这份字典」的场景

不适用场景: - 任意未知结构的 JSON 压缩(需要先分析出 replacements) - 对压缩率有精确要求的场景(数字不可控)


一句话结论

condense-json 通过标记引用({"$": id} / {"$r": [...]} / merge patch)实现 JSON 内重复数据的无损压缩,已集成进 LLM CLI 生产环境,特别适合 LLM 推理日志中 tool schema / system prompt 反复出现的场景——维护好 replacements 映射,就能自动减小日志体积。