惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
博客园 - 司徒正美
I
InfoQ
宝玉的分享
宝玉的分享
G
Google Developers Blog
J
Java Code Geeks
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
罗磊的独立博客
腾讯CDC
F
Fortinet All Blogs
A
About on SuperTechFans
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Recent Announcements
Recent Announcements
Last Week in AI
Last Week in AI
B
Blog RSS Feed
博客园 - 聂微东
D
DataBreaches.Net
Hugging Face - Blog
Hugging Face - Blog
The Cloudflare Blog
L
LangChain Blog
Microsoft Azure Blog
Microsoft Azure Blog
aimingoo的专栏
aimingoo的专栏

V2EX

我用 AI 写代码,但终端管理反而成了累赘——于是我做了 codux [调研] 各位在公司都用什么 ide 和 agent 写代码? 老运维 share 一个运维平台 看到有公司考核 token 指标,很好奇大家上个月的 AI 账单是多少 GLM-Coding 调用持续报错: z.ai 的 Lite 套餐几乎无法使用,官方 Pro/Max 是否稳定? 现在还有什么渠道可以稳定安全地使用 Claude 吗? 上海漕河泾内推,本组有 2 个 hc,一个后端,一个前端,预算都是 20k 左右,不打卡,氛围好 如果 V2EX 上有一组不永久保存聊天记录(比如只保存 7 天或者 24 小时)的聊天室,那么会开启哪些有用或者有趣的可能? gemini cli 貌似挂了,一直返回 403 第一次在自媒体上赚到钱 收集了最近在使用的低价 GPT, Gemini,邮箱等 AI 会员的小店合集 讨论个大实话:现在企业还在说 AI 编程提效 20%, 30%的,真的太落后,没用懂 AI。因为包括很多前沿公司,已经狂奔到提效 200%-500%的情况 [招聘][远程][币安] 前端/后端/QA/iOS/Android 至少 3 年以上经验 目前有大量 HC 欢迎投递 Chatgpt Pro 用量用不完的可以开这些设置 面试的时候好像遇到钓鱼了,给各位避个坑 cursor 年续费 22 号到期, 自动续费是否还是老的计次套餐呢 被两件破事毁掉的一下午,琐碎的内耗消磨人的精力 使用 Planet 存储 Codex 的会话或者重要信息 如果业务部门领导不要你开发功能,而是要求你教会它用 claude code 开发功能,你会怎么做? 分享一个 MacOS 接绿联 CM818 USB 转 DP 转接器使用感受 我的 HR 朋友 10 年老 Java ,非全大专,大家帮忙看看简历 开源了一个 AI 口语练习工具,音素级发音评分,完全免费可自部署 V2EX 上有哪些你觉得很有趣、印象深刻的妹纸? 字节为啥不出个国内版 Vercel? 有在大马的朋友吗? 问个运营商问题 你们在有领导的公司大群发过的最大胆的消息是什么 公司裁员,目前没有工作。想试试摆摊,做一个移动鲜啤打酒车 我的硬盘 Memblaze Pblaze 5 Linux 下不识别,给 Linux 内核提交了补丁, AI 说有望被合并 只有我一个人觉得 codex 不好用?
实现一个流式 Json 解析器,解决既要求结构化数据又需要实时...
wantDoraemon · 2025-12-16 · via V2EX

为什么需要流式 JSON 解析?

AI 模型的输出通常是逐字生成的流式数据,尤其是在实时对话交互的场景中,用户希望能即时看到 AI 输出内容的过程。而另一方面,我们在很多场景下需要 AI 模型输出结构化的内容,方便我们后续处理和展示。我们可以通过 markdown 的标题格式来组织 AI 的输出,但是 markdown 的无法做到强约束。如果需要保证强结构化,那么我们一般会采用 JSON 格式,对此很多模型都有强制约束输出 Json 的参数。 这其中有两大问题:

  1. 高延迟 :如果使用 Json 格式,传统的 Json.Parse 方法必须等待整个字符串接收完毕才能解析,这会导致高延迟问题。用户无法实时看到 AI 的思考过程,体验大打折扣。
  2. 交互不稳定 :如果使用 Markdown 格式,AI 输出可能不符合预期,导致前端展示异常。

因此,我们需要一种能够边接收数据边解析的方案,确保用户实时看到 AI 的输出,同时保证解析的健壮性。


场景

例如我们在理解用户问题这个场景时,既想要结构化的数据,又想要实时的将结果反馈到前端 preview

设计目标

为了解决上述问题,我们设计了一款流式 JSON 解析器,目标包括:

  1. 实时性:支持逐字符解析,边接收边触发回调。
  2. 路径订阅:允许用户按需订阅 JSON 中的特定路径(如 $.nodes[*].title),减少无效数据处理。
  3. 增量输出:针对字符串值,仅发送新增部分,避免重复传递完整值。
  4. 健壮性:即使 JSON 格式不完整或后续部分有错误,已解析的数据也能正常使用。

核心实现

我们的解析器基于手写的有限状态机( FSM ),逐字符处理流式数据。以下是实现的关键组件和流程:

1. StreamingJsonParser (流式解析器)

  • 状态机设计:解析器通过状态机维护当前解析上下文,支持对象、数组、字符串、数字等 JSON 元素的逐字符解析。
  • 路径维护:通过栈结构记录当前解析路径(如 ["nodes", 0, "title"]),用于路径匹配和回调触发。
  • 增量输出:针对字符串值,记录上次发送的位置,仅发送新增部分。

状态机的核心逻辑如下: 状态机

2. SimplePathMatcher (路径匹配器)

  • 路径解析:支持将路径模式(如 $.nodes[*].title)解析为数组形式(如 ["nodes", "*", "title"])。
  • 通配符匹配:支持 * 通配符,用于匹配数组中的任意索引。
  • 回调触发:当解析器识别到匹配路径的数据时,立即触发用户注册的回调函数。

增量与实时模式

解析器支持两种模式:

  1. 实时模式( realtime=true ):在值尚未最终确定时,依据当前缓冲区内容触发回调,适合逐字生成的场景。
  2. 增量模式( incremental=true ):针对字符串值,仅发送新增部分,避免重复传递完整值。

以下是增量解析的示例:

matcher := utils.NewSimplePathMatcher()
matcher.On("$.choices[0].delta", func(value interface{}, path []interface{}) {
    fmt.Printf("path=%v, value=%v\n", path, value)
})

parser := utils.NewStreamingJsonParser(matcher, true, true)
_ = parser.Write("{\"choices\":[{\"delta\":\"")
_ = parser.Write("Hel") // 增量触发回调:"Hel"
_ = parser.Write("lo\"}]}\n") // 增量触发回调:"lo",结束后不再发送整串
_ = parser.End()

性能与内存优化

  • 轻量高效:手写状态机避免引入完整 JSON 库,适合流式场景。
  • 增量缓存:通过记录上次发送位置,减少重复回调数据。
  • 路径匹配优化:使用切片维护路径,避免过度复制。

常见问题与改进方向

  1. 字符串转义:支持常见转义 \n, \t, \r, \\, \", \/, \b, \f;当前未支持 \uXXXX Unicode 转义序列,可按需扩展。
  2. 路径匹配:目前为精确匹配,可考虑支持前缀匹配或更完整的 JsonPath 语法。
  3. 增量支持:目前仅支持字符串值的增量输出,未来可扩展至对象和数组。

文档&代码实现

代码使用 Golang 实现,如果需要使用其他语言,可以让 AI 翻译一下即可。

通过自研流式 JSON 解析器,成功解决了 AI 应用中实时性和结构化输出的难题。希望这次分享能为有类似需求的开发者提供参考。