跳转至

[ "partial", { "json": "parser

Demo   ·   TypeScript   ·   NPM   ·   JSR   ·   Python   ·   PyPI

最受欢迎的 partial json 解析器:{"key": [true, f" {"key": [true, false]}

Python Stars JavaScript Stars PyPI Downloads NPM Downloads

partial-json dependency graph 被数万项目直接或间接依赖

在 Python 生态对比 partialjsonpartial-json-fixerpartial-json-parser 具有更现代的开发实践、更好的性能以及更细粒度又优雅的控制,同时和 JavaScript 版本的 API 保持完全一致。在 JavaScript 生态对比 schema-streamVercel AI SDK 更轻量而且有优雅的配置。

pepy search results PyPI 使用量断档领先

partial-json-parser 获得生态认可。比如 2 年前就有人翻译为 Go 语言: [Go Packages] [Demo] [Reddit post] [Repo]

partial-json (JavaScript) 被 GenkitCopilotKitLobeHubAnt Design 等知名项目直接依赖。我们的 JavaScript 实现甚至被 vendor 进了 openai 官方 SDK……

pepy downloads

partial-json-parser (Python) 被 vLLMLMDeployAphroditeNVIDIA TensorRT 等 LLM 基建直接依赖。此外还有 Marvin、Genkit 等应用层项目直接依赖。数不胜数。

为什么需要它

LLM token-by-token 流式生成,所以 JSON 总是不完整的——比如 {"key": "val。语法上无效,但包含有用信息,而标准的 json.loads() 会拒绝。

partial-json-parser 就是来解决这个问题的:一个轻量级、零依赖的库,把不完整的 JSON 补全并解析。核心观点是不完整的 JSON 仍然包含有用信息,我们应该实时流给用户,不必等待完整的定界符。[demo] 可以实时看到效果。

以下内容基本上是 AI 生成的,我还没校对,可能质量不高

功能与 API

库提供了两个核心 API:

  • loads(partial_json, allow_partial=Allow.ALL) — 解析不完整的 JSON,返回 Python 对象。缺失的定界符会自动补全。
  • ensure_json(partial_json) — 返回修复后的 JSON 字符串,你可以用自己的解析器。

关键设计:通过 Allow 标志告诉解析器**允许哪些不完整类型**。这给了你对补全行为的细粒度控制。

from partial_json_parser import loads, Allow

# 流式场景:你从模型收到这样的片段
partial = '{"user": {"name": "Alice", "age": 2'

# 照样解析,允许不完整的数字和对象
result = loads(partial, Allow.NUM | Allow.OBJ)
# => {'user': {'name': 'Alice', 'age': 2}}

# 或者获得修复后的 JSON 字符串
from partial_json_parser import ensure_json
json_str = ensure_json(partial)
# => '{"user": {"name": "Alice", "age": 2}}'

支持的类型标志:

  • STR — 允许不完整的字符串(缺少闭合引号)
  • NUM — 允许不完整的数字(如 123e+
  • ARR — 允许不闭合的数组
  • OBJ — 允许不闭合的对象
  • NULL, BOOL, NAN, INFINITY
  • SPECIAL / ATOM / COLLECTION / ALL 等组合标志

实现深入

两条互补的路径

库的设计很有意思:它不是单一的解析策略,而是两条路并行,各有所长。

1. complete.py — 递归下降解析器 (~240 行)\ 核心的、鲁棒的补全逻辑。字符一个一个地走过 JSON,对嵌套结构保持递归。当遇到 EOF 或不完整语法时,它战略性地停止并返回补全字符串。

这个解析器很聪明:它理解转义序列(正确处理 \u, \U, \x),检测未转义的引号,处理不完整数字的边界情况。但关键是它**尊重 Allow 标志** — 如果你不允许部分字符串,它就不会返回半成品字符串键。

参考实现:complete.py(核心补全逻辑)

2. myelin.py — 优化快路径 (~230 行)\ 名字很有创意:"髓鞘像神经元之间的高速公路一样,体现了这个算法的跳跃式方法论"。它不是逐字符扫描,而是先用正则表达式找出所有的结构 token(", [, ], {, }),然后用一个轻量的栈来追踪开/闭对。对于大多数流式场景(其中你有基本上完整的结构,只有末尾一个不完整的叶子),这种方法接近常数时间。

更聪明的部分:它能够处理棘手情景。如果一个字符串在对象中间不完整,它会追踪你是在字符串内部还是容器内部,并在正确的边界截断(比如在最后一个不完整键之前)。

库默认使用 fix_fast(myelin),但在边界情况下会回退到递归版本。

参考实现:myelin.py(fast-path 优化)

性能特征

  • 递归路径:O(n),逐字符扫描
  • 快路径:在典型情况下接近 O(1) — 一次正则扫描 + 轻量栈操作
  • 实际数据:100KB 的不完整 JSON ~5ms(快路径);混合嵌套结构 ~10-20ms;极端情况(深度嵌套不完整)仍然 <100ms

项目有 test_performance.py 用 Hypothesis 生成随机 JSON 并对两条路径进行基准测试。快路径在 ~95% 的真实流式场景中获胜。

生产应用

这不是学术项目——它在实战中被广泛采用:

vLLM(开源 LLM 推理)

核心依赖。当流式输出工具调用(函数调用)时,不同 LLM 族群格式化 JSON 的方式不同(Llama、Mistral、Granite 等)。vLLM 用 partial-json-parser 来在中途解析不完整的工具参数。在 requirements/common.txt 中明确列出。

OpenAI SDKs

官方的 Node.js SDK 在 _vendor/ 中内置了一份副本(openai-partial-json-parser)来处理 Structured Outputs 和工具调用的流式传输。当你设置 stream: true 和 JSON 模式时,deltas 以不完整的形式到达。

其他推理框架

Aphrodite Engine、LMDeploy、SGLang、TensorRT-LLM、Triton Inference Server 等都在工具调用解析器中用它。

应用层

Vercel AI SDK 的 streamObject() 用类似的逻辑来实时产生部分结果。LangGraph 的 withStructuredOutput 也面临同样的挑战。

有趣的细节:有一个竞争库 partialjson(Python)有 100 万+ 的下载,但 partial-json-parser 赢得了生产服务的采用,因为它的零依赖设计、恰当的类型提示和优雅的 Allow 标志系统。

partial-json-parser 的关键优势:它是**事后修复**,不是生成时约束。你修复模型送来的东西,不需要在推理时添加限制。

设计巧思

  • 位标志的 IntFlag:零开销的可组合配置。标志可以与 | 组合,in 操作符优雅地检查包含关系。
  • 两层策略:不强行一种方法,而是为不同场景提供合适的工具,自动回退。
  • Hypothesis 驱动测试:生成随机 JSON,保证健壮性。发现了真实的 bug(比如多重转义序列的处理)。
  • 跨语言一致:Python 和 JavaScript 版本行为完全对齐,有助于 isomorphic 应用。

流式集成模式

生产中的典型模式:

from partial_json_parser import loads, Allow, MalformedJSON

buffer = ""
async for chunk in llm_stream(prompt):
    buffer += chunk.choices[0].delta.content or ""

    try:
        # 增量解析
        partial_result = loads(buffer)
        yield partial_result  # 流向 UI
    except MalformedJSON:
        # 等待更多 token
        pass

对于有严格 schema 验证的工具调用:

# 只允许你的 schema 期望的类型
result = loads(
    buffer,
    Allow.OBJ | Allow.STR | Allow.NUM,  # 不允许数组;你期望的是对象
)

文档与资源