Go 1.27 发布新的 JSON 标准库 API:encoding/json/v2 与 jsontext
来源标题:A new Go API for JSON
Go 1.27 正式引入稳定的 encoding/json/v2 和 encoding/json/jsontext 包,替代原有 encoding/json 的实验性改进。这两个包此前以实验形式发布,经反馈和打磨后转为正式可用,旨在解决 encoding/json 长期存在的问题。
以下正文由 AI 翻译自 Go Blog(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
Go 博客
全新的 Go JSON API
Michael Pratt、Joe Tsai、Daniel Martí、Johan Brandhorst-Satzkorn、Roger Peppe、Chris Hines 和 Damien Neil 2026 年 11 月 4 日
引言
JavaScript Object Notation(JSON)是一种简单的数据交换格式,也是互联网上最流行的数据格式,Go 程序广泛地读写它。 早在 Go 1.0 之前,Go 标准库就已经包含了对 JSON 的支持。 我们第一次谈论 Go 对 JSON 的支持还是在 15 年前! 随着时间推移,软件包会随用户需求而演进,encoding/json 也不例外。 去年,我们介绍了一个全新的实验性 JSON API,旨在带来人们期待已久的改进与修复。 经过进一步的反馈和打磨,新的 encoding/json/v2 和 encoding/json/jsontext 包已经稳定,并在 Go 1.27 中正式可用! 本文是介绍该实验的那篇文章的更新版,描述了 encoding/json 存在的问题,并对新包做了介绍。
encoding/json 存在的问题
总体而言,encoding/json 经受住了考验。 将任意 Go 类型以某种默认的 JSON 表示进行编组和解组,再加上自定义表示的能力,这一思路已被证明具有高度的灵活性。 然而,自其引入以来的这些年里,众多用户发现了许多不足之处。
行为缺陷
encoding/json 存在多种行为缺陷:
对 JSON 语法的处理不够精确:多年来,JSON 的标准化程度不断提高,以便程序之间能够正确通信。 总体而言,解码器在拒绝有歧义的输入方面变得更加严格,以减少两种实现针对某个特定 JSON 值产生不同(但都成功)的解释的可能性。
encoding/json 目前接受非法的 UTF-8,而最新的互联网标准(RFC 8259)要求使用合法的 UTF-8。 默认行为应当在遇到非法 UTF-8 时报告错误,而不是引入静默的数据损坏,后者可能在下游引发问题。
encoding/json 目前接受含有重复成员名的对象。RFC 8259 没有规定如何处理重复名称, 因此实现可以自由选择任意一个值、合并这些值、丢弃这些值,或者报告错误。 重复名称的存在会导致某个 JSON 值缺乏普遍公认的含义。 这可能被攻击者在安全应用中利用,而且此前已被利用过(如 CVE-2017-12635)。 默认行为应当以安全为重,拒绝重复名称。
泄露切片和映射的 nil 性质:JSON 常用于与其他程序通信,而这些程序的 JSON 实现不允许将 null 解组到预期为 JSON 数组或对象的数据类型中。 由于 encoding/json 会将 nil 切片或映射编组为 JSON null,这可能导致其他实现在解组时出错。 一项调查表明,大多数 Go 用户希望默认情况下将 nil 切片和映射编组为空 JSON 数组或对象。
大小写不敏感的解组:解组时,JSON 对象成员名通过大小写不敏感的匹配解析为 Go 结构体字段名。 这是一个令人意外的默认行为,是一个潜在的安全漏洞,也是一种性能限制。
方法调用不一致:由于某个实现细节,encoding/json 对声明在指针接收者上的 MarshalJSON 方法的调用并不一致。 虽然这被视为一个 bug,但无法修复,因为太多应用依赖当前行为。
API 缺陷
encoding/json 的 API 可能难以使用或限制过多:
从 io.Reader 正确解组很困难。用户常常写出 json.NewDecoder(r).Decode(v), 而这种方式无法拒绝输入末尾的多余内容。
选项可以在 Encoder 和 Decoder 类型上设置,却无法与 Marshal 和 Unmarshal 函数一起使用。 同样,实现 Marshaler 和 Unmarshaler 接口的类型也无法使用这些选项,而且没有办法将选项沿调用栈向下传递。 例如,在调用自定义 UnmarshalJSON 方法时,Decoder.DisallowUnknownFields 选项会失去效果。
Compact、Indent 和 HTMLEscape 函数写入的是 bytes.Buffer,而不是 []byte 或 io.Writer 之类更灵活的目标。 这限制了这些函数的可用性。
性能限制
暂且不论内部实现细节,公开的 API 本身就决定了某些性能限制:
MarshalJSON:MarshalJSON 接口方法强制实现方为返回的 []byte 分配内存。此外,其语义要求 encoding/json 验证结果是合法的 JSON,并且还要重新格式化以匹配指定的缩进。
UnmarshalJSON:UnmarshalJSON 接口方法要求提供完整的 JSON 值(不能有任何尾随数据)。 这迫使 encoding/json 在调用 UnmarshalJSON 之前,必须完整解析待解组的 JSON 值以确定其结束位置。 之后,UnmarshalJSON 方法本身又必须再次解析所提供的 JSON 值。
缺乏流式处理:尽管 Encoder 和 Decoder 类型基于 io.Writer 或 io.Reader 工作,它们却会将整个 JSON 值缓冲在内存中。 用于读取单个 token 的 Decoder.Token 方法分配开销很大,而且没有用于写入 token 的对应 API。 此外,如果 MarshalJSON 或 UnmarshalJSON 方法的实现递归调用了 Marshal 或 Unmarshal 函数,性能就会退化为二次方级别。
尝试直接修复 encoding/json
引入一个新的、不兼容的主版本号是一个需要慎重考虑的决定。 如果可能,我们应当尝试修复现有的包。 添加新功能相对容易,但修改现有功能却很困难。 不幸的是,这些问题都是现有 API 的固有后果,
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。