You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go中解析不完整/部分JSON?

在Go中解析不完整/部分JSON字符串为map/interface类型

针对OpenAI流式聊天补全返回的分片JSON(比如函数调用的响应被拆分成多段SSE消息),需要实时解析不完整的JSON片段并转换为map[string]any类型,以下是具体实现方案:

核心思路

标准库encoding/json仅支持完整有效的JSON解析,无法处理未闭合的字符串、未完成的键值对等情况。我们需要自定义一个状态机驱动的解析器,逐字符处理输入,维护当前解析状态(如是否处于字符串中、当前正在处理的键/值、已完成的键值对),从而实时输出已解析的有效内容。

实现代码

package main

import (
	"fmt"
	"strings"
	"unicode"
)

type PartialJSONParser struct {
	result       map[string]any
	currentKey   string
	currentValue strings.Builder
	inString     bool
	escaped      bool
	expectingKey bool
	expectingVal bool
}

func NewPartialJSONParser() *PartialJSONParser {
	return &PartialJSONParser{
		result:       make(map[string]any),
		expectingKey: true,
	}
}

// Feed 输入一段JSON分片,更新解析状态并返回当前已解析的结果
func (p *PartialJSONParser) Feed(input string) map[string]any {
	for _, r := range input {
		switch {
		case p.inString:
			if p.escaped {
				p.currentValue.WriteRune(r)
				p.escaped = false
				continue
			}
			if r == '\\' {
				p.escaped = true
				continue
			}
			if r == '"' {
				p.inString = false
				if p.expectingKey {
					p.currentKey = p.currentValue.String()
					p.currentValue.Reset()
					p.expectingKey = false
					p.expectingVal = true
				} else if p.expectingVal {
					p.result[p.currentKey] = p.currentValue.String()
					p.currentValue.Reset()
					p.expectingVal = false
					p.expectingKey = true
				}
			} else {
				p.currentValue.WriteRune(r)
			}
		case unicode.IsSpace(r):
			continue
		case r == '{':
			// 忽略已处理的开头大括号
		case r == '"':
			p.inString = true
		case r == ':':
			// 键已读取完成,准备读取值
			p.expectingKey = false
			p.expectingVal = true
		case r == ',':
			// 如果当前正在处理值,说明值是未闭合的字符串,暂存当前内容
			if p.expectingVal && p.currentValue.Len() > 0 {
				p.result[p.currentKey] = p.currentValue.String()
				p.currentValue.Reset()
				p.expectingVal = false
				p.expectingKey = true
			}
		case r == '}':
			// 结束时处理未完成的键值对
			if p.expectingVal && p.currentValue.Len() > 0 {
				p.result[p.currentKey] = p.currentValue.String()
			}
		default:
			// 处理非字符串类型的部分值(如数字、布尔值),这里简化处理为字符串
			if p.expectingVal {
				p.currentValue.WriteRune(r)
			}
		}
	}

	// 返回当前已解析的结果副本
	copyResult := make(map[string]any, len(p.result))
	for k, v := range p.result {
		copyResult[k] = v
	}
	// 如果有未完成的键值对,也加入结果
	if p.expectingVal && p.currentValue.Len() > 0 {
		copyResult[p.currentKey] = p.currentValue.String()
	}
	return copyResult
}

func main() {
	// 模拟OpenAI流式返回的分片JSON
	chunks := []string{`{"f`, `iel`, `d":`, `"val`, `ue"}`}

	parser := NewPartialJSONParser()
	for i, chunk := range chunks {
		result := parser.Feed(chunk)
		fmt.Printf("处理第%d段后结果: %v\n", i+1, result)
	}
	// 输出:
	// 处理第1段后结果: map[]
	// 处理第2段后结果: map[]
	// 处理第3段后结果: map[]
	// 处理第4段后结果: map[field:val]
	// 处理第5段后结果: map[field:value]
}

说明

  1. 状态管理:解析器通过inString、escaped、expectingKey、expectingVal等状态变量跟踪当前解析位置,处理字符串转义、键值对切换等场景。
  2. 分片处理:每次调用Feed方法输入一段JSON分片后,都会返回当前已解析的有效键值对,支持实时更新UI。
  3. 扩展性:当前实现仅处理字符串类型的值,可根据需求扩展支持数字、布尔值、嵌套对象/数组等类型的部分解析。

内容的提问来源于stack exchange,提问作者Emre Kayan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 08:06:00