Go encoding/json/v2提案:JSON处理新引擎

9d1c8950c830898d170fd03e5645a720.jpeg请点击上方蓝字TonyBai订阅公众号!

e002cfc029f4261833352133e49255e4.png

Go标准库中的encoding/json包,作为Go社区广泛使用的JSON处理工具,至今已走过十余年[1]。凭借其将JSON数据与原生Go类型相互转换的能力、通过struct tag自定义字段表示的灵活性,以及Go类型自定义JSON格式的特性,赢得了Go开发者的青睐。

然而,随着时间的推移,encoding/json的局限性也逐渐显现。孤立地解决这些问题可能会导致非正交特性之间产生意外的交互。因此,Go团队在2023年下旬发起了关于encoding/json/v2的讨论[2],旨在全面审视现有encoding/json包,并提出一个面向未来十年的Go JSON处理方案,打造一个 JSON处理新引擎

经过近一年多的讨论、设计调整以及参考实现的优化,Go团队于近期正式提出了关于encoding/json/v2的提案issue[3]。在该issue中,Go团队梳理并总结了讨论结果以及初始设计与后续调整之间的差异,以供Go社区进一步审阅与反馈。

为了让大家更好地了解该提案issue的核心内容,本文将对提案的背景、主要内容、相对于v1版本的主要改进,以及与v1版本的联系等进行全面介绍,希望通过这篇文章,大家能够及时了解到Go标准库json包的演进与变化。

1. 提案背景:现有encoding/json包的局限与改进的需求

在过去十年中,开发者在使用encoding/json的过程中,逐渐意识到了它在功能、API设计、性能和行为上存在的不足。这些问题可以归纳为以下几个方面,也正是encoding/json/v2提案希望解决的核心痛点:

1.1 功能缺失 (Missing functionality)

尽管encoding/json功能完善,但仍存在一些重要的功能缺失,社区也为此提出了诸多Feature Request,其中最突出的包括:

  • time.Time的自定义格式化 (#21990): 缺乏灵活的方式来指定time.Time类型在JSON中的格式,例如自定义日期时间字符串格式。

  • Marshal时忽略特定Go值 (#11939, #22480, #50480, #29310, #52803, #45669): 现有的omitempty标签在某些场景下无法满足需求,开发者希望更精细地控制哪些Go值在Marshal时被忽略,例如忽略零值、空值或特定条件下的值。Go 1.24版本[4]增加了omitzero tag[5],将在一定层度缓解这个问题。

  • 将nil切片和mapMarshal为空JSON数组和对象 (#37711, #27589): encoding/json默认将nil切片和mapMarshal为JSONnull,但在某些场景下,开发者更期望将其Marshal为空的JSON数组[]和对象{}。

  • 无Go嵌入的Inline类型 (#6213): 希望能够更灵活地将Go类型内联到JSON对象中,而无需依赖Go的struct嵌入机制。

虽然这些功能缺失大部分可以通过向现有encoding/json包添加新功能来解决,但可能会导致API变得臃肿和复杂。

1.2 API 设计缺陷 (API deficiencies)

encoding/json的API设计存在一些尖锐或限制性的问题,影响了开发者的使用体验:

  • 难以正确地从io.Reader进行Unmarshal: 常用的json.NewDecoder(r).Decode(v)方法并不能正确处理JSON payload末尾的垃圾数据 (#36225),容易导致数据解析错误。

  • Marshal和Unmarshal函数无法使用Options: 虽然Encoder和Decoder类型支持Options配置,但Marshal和Unmarshal函数却无法使用,同样,实现Marshaler和Unmarshaler接口的类型也无法利用Options,缺乏选项配置的传递机制(#41144)。

  • Compact, Indent, HTMLEscape函数输出目标受限: 这些格式化函数只能将结果写入bytes.Buffer,而不是更灵活的[]byte或io.Writer,限制了函数的使用场景。

这些API缺陷可以通过向现有encoding/json包引入新的API来修复,但这可能会导致同一个任务在同一个包中存在多种不同的实现方式,增加学习成本和使用困惑。

1.3 性能限制 (Performance limitations)

encoding/json的性能表现一直备受关注,存在诸多限制性能提升的因素:

  • MarshalJSON接口: 强制实现者分配[]byte返回值,且encoding/json需要再次解析返回值以验证JSON的有效性并重新格式化,造成不必要的性能开销。

  • UnmarshalJSON接口: 要求提供完整的JSON value,导致encoding/json需要预先完整解析JSON值以确定边界,之后UnmarshalJSON方法本身还需要再次解析,如果UnmarshalJSON递归调用Unmarshal,则会导致O(N²)的性能退化,例如Kubernetes kube-openapi项目在Unmarshalspec.Swagger时遇到的性能瓶颈 (kubernetes/kube-openapi#315)。

  • Encoder.WriteToken: 缺乏流式Encoder API,虽然提案已被接受(#40127),但尚未实现,且可能同样存在性能问题。

  • Decoder.Token: Token类型是一个接口,可以容纳多种类型 (Delim, bool, float64, Number, string, nil),当boxing数字或字符串到Token接口类型时,会频繁发生内存分配 (#40128)。

  • 缺乏真正的流式处理: 即使Encoder.Encode和Decoder.Decode方法操作io.Writer和io.Reader,它们仍然会将整个JSON value缓冲到内存中,需要二次扫描JSON,与流式处理的初衷背道而驰 (#33714, #7872, #11046)。

encoding/json应该默认以真正的流式方式操作io.Writer和io.Reader。缓冲整个JSON value违背了使用io.Reader和i

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值