go-json-experiment/json
GitHub: go-json-experiment/json
这是一个实验性的 Go JSON 序列化库,旨在改进标准库 encoding/json 的性能、API 灵活性和行为规范。
Stars: 545 | Forks: 30
# JSON 序列化(v2)
[](https://pkg.go.dev/github.com/go-json-experiment/json)
[](https://github.com/go-json-experiment/json/actions)
本模块托管了 v2 `encoding/json` 的实验性实现。
该 API 不稳定,将定期进行破坏性更改。
请勿在公开可用的模块中依赖此包。
任何导致破坏性 API 或行为更改的提交,其提交信息顶部附近都会被标记为字符串 "WARNING: "。
升级该模块时,检查提交更改列表是您的责任。并非所有破坏性更改都会导致构建失败。
## 当前状态
一个 [关于将此模块作为 `encoding/json/v2` 和 `encoding/json/jsontext` 纳入 Go 的提案](https://github.com/golang/go/issues/71497)
已于 2025-01-30 在 Go 的 GitHub 项目中启动。请在那里提供您的反馈。
目前,此模块作为 Go 实验在 Go 标准库中可用。
参阅 ["用于 JSON 的新实验性 Go API"](https://go.dev/blog/jsonv2-exp) 了解更多详情。
2025-11-20,Go 项目成立了一个 `json/v2` 工作组,
成员包括 @aclements、@neild、@prattmic、@ChrisHines 和 @dsnet,
旨在审查在准备正式纳入标准库过程中的未决问题。
工作组每周开会,会议记录在 [issue #76406](https://github.com/golang/go/issues/76406) 中。
参阅 [项目仪表板](https://github.com/orgs/golang/projects/50/views/1)
了解工作组需要解决的剩余问题。
"审查中" 和 "需要审查" 类别需要被清空
才能发布 `json/v2`。
在可预见的未来,此仓库将继续导入并镜像上游 Go 项目。
因此,代码更改应在上游 [Go 项目](https://go.googlesource.com/go/) 进行,而不是在此处。
使用 `goexperiment.jsonv2` 标签构建此模块将导致
该模块在底层使用 `encoding/json/v2` 包,
否则将提供一个不同的 `json/v2` 实现。
## 目标和目的
* **大致向后兼容:** 如果可能,v2 应旨在在 API 和默认行为方面
与 v1 大致兼容,以简化迁移。
例如,`Marshal` 和 `Unmarshal` 函数是 v1 包中使用最广泛的
声明。v2 中的等效功能采用相同的名称并具有大致兼容的签名似乎是合理的。
在行为上,我们应力求 95% 到 99% 的向后兼容性。
我们不追求 100% 兼容,因为我们希望保留破坏某些现在被认为是错误行为的自由。
存在使 v2 实现达到 100% 兼容的选项,
但这不会是默认设置。
* **更灵活:** 有一个
[很长的功能请求列表](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+encoding%2Fjson+in%3Atitle)。
我们应旨在提供最灵活的功能,以满足大多数用例。
我们不希望过度适配 v2 API 来处理每一个可能的用例。
理想情况下,提供的功能本质上应是正交的,以便
任何功能组合都能产生尽可能少的令人惊讶的边缘情况。
* **更高效:** JSON 序列化被广泛使用,任何一点额外的
性能提升都将受到极大赞赏。v1 的一些不常用行为
可能会为了更好的性能而被舍弃。例如,
尽管 `Encoder` 和 `Decoder` 操作 `io.Writer` 和 `io.Reader`,
但它们并非以真正的流式方式运行,
导致了性能损失。v2 实现应默认旨在真正实现流式处理(参见 [#33714](https://golang.org/issue/33714))。
* **易于使用(难以误用):** v2 API 应旨在使
常见情况变得容易,不常见的情况至少成为可能。
API 应避免与用户预期相悖的行为,
这可能导致细微的错误(参见 [#36225](https://golang.org/issue/36225))。
* **v1 和 v2 的可维护性:** 由于 v1 实现必须永远保留,
如果 v1 能在底层使用 v2 实现将是有益的,
从而在未来减少维护负担。这可能意味着
v2 相对于 v1 的行为变化需要作为选项暴露出来。
* **避免使用 unsafe:** 标准库包通常避免使用
`unsafe` 包,即使它可能提供性能提升。
我们旨在保持这一特性。
## 开发
此模块主要由
[@dsnet](https://github.com/dsnet)、
[@mvdan](https://github.com/mvdan) 和
[@johanbrandhorst](https://github.com/johanbrandhorst) 开发,
并由
[@rogpeppe](https://github.com/rogpeppe)、
[@ChrisHines](https://github.com/ChrisHines) 和
[@rsc](https://github.com/rsc) 提供反馈。
语义讨论定期举行,
[过往会议记录可在此处找到](https://docs.google.com/document/d/1rovrOTd-wTawGMPPlPuKhwXaYBg9VszTXR9AQQL5LfI/edit?usp=sharing)。
## 设计概览
此包旨在提供语法和语义之间的清晰分离。
语法处理 JSON 的结构表示(如
[RFC 4627](https://tools.ietf.org/html/rfc4627)、
[RFC 7159](https://tools.ietf.org/html/rfc7159)、
[RFC 7493](https://tools.ietf.org/html/rfc7493)、
[RFC 8259](https://tools.ietf.org/html/rfc8259) 和
[RFC 8785](https://tools.ietf.org/html/rfc8785) 所指定)。
语义处理语法数据作为可用应用程序数据的含义。
`Encoder` 和 `Decoder` 类型是流式分词器,关注 JSON 数据的
打包或解析。它们操作 `Token` 和 `Value` 类型,
这些类型代表了 JSON 中可表示的通用数据结构。
`Encoder` 和 `Decoder` 不旨在提供数据的任何解释。
像 `Marshal`、`MarshalWrite`、`MarshalEncode`、`Unmarshal`、
`UnmarshalRead` 和 `UnmarshalDecode` 这样的函数通过将
任意 Go 类型与其某种 JSON 表示形式(存储在
`[]byte`、`io.Writer`、`io.Reader`、`Encoder` 或 `Decoder` 等数据类型中)关联起来,提供语义意义。

此图提供了 v2 `json` 和 `jsontext` 包的高级概览。
紫色块代表类型,蓝色块代表函数或方法。
箭头及其方向代表数据的近似流向。
图的下半部分包含仅与语法相关的功能(由 `jsontext` 包实现),
而上半部分包含为语法数据(由下半部分处理)
赋予语义意义的功能(由 v2 `json` 包实现)。
与 v1 `encoding/json` 不同,选项被表示为独立的类型,
而不是 `Encoder` 或 `Decoder` 类型上的设置器方法。
一些选项在语法层影响 JSON 序列化,
而另一些在语义层影响它。
一些选项仅在解码时影响 JSON,
而另一些在编码时影响 JSON。
## 行为变更
v2 `json` 包相对于 v1 `json` 包更改了 `Marshal` 和 `Unmarshal` 的默认行为,
使其更合理。
其中一些行为变更有选项和变通方法,以选择
与 v1 提供的行为类似的行为。
下表概述了这些变更:
| v1 | v2 | 详情 |
| -- | -- | ---- |
| JSON 对象成员通过**不区分大小写的名称匹配**解组到 Go 结构体中。 | JSON 对象成员通过**区分大小写的名称匹配**解组到 Go 结构体中。 | [CaseSensitivity](/v1/diff_test.go#:~:text=TestCaseSensitivity) |
| 当编组 Go 结构体时,标记为 `omitempty` 的结构体字段在**字段值是空的 Go 值**时会被省略,空值定义为 false、0、nil 指针、nil 接口值以及任何空数组、切片、映射或字符串。 | 当编组 Go 结构体时,标记为 `omitempty` 的结构体字段在**字段值会编码为空 JSON 值**时会被省略,空 JSON 值定义为 JSON null,或空的 JSON 字符串、对象或数组。 | [OmitEmptyOption](/v1/diff_test.go#:~:text=TestOmitEmptyOption) |
| `string` 选项**会影响** Go 字符串和布尔值。 | `string` 选项**不会影响** Go 字符串或布尔值。 | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| `string` 选项**不会递归影响** Go 字段值的子值。 | `string` 选项**会递归影响** Go 字段值的子值。 | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| `string` 选项**有时接受**在 JSON 字符串中转义的 JSON null。 | `string` 选项**从不接受**在 JSON 字符串中转义的 JSON null。 | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| nil Go 切片被编组为 **JSON null**。 | nil Go 切片被编组为 **空 JSON 数组**。 | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) |
| nil Go 映射被编组为 **JSON null**。 | nil Go 映射被编组为 **空 JSON 对象**。 | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) |
| Go 数组可以从**任意长度的 JSON 数组**解组。 | Go 数组必须从**相同长度的 JSON 数组**解组。 | [Arrays](/v1/diff_test.go#:~:text=Arrays) |
| Go 字节数组表示为 **JSON 数字的 JSON 数组**。 | Go 字节数组表示为 **Base64 编码的 JSON 字符串**。 | [ByteArrays](/v1/diff_test.go#:~:text=TestByteArrays) |
| 声明在指针接收者上的 `MarshalJSON` 和 `UnmarshalJSON` 方法**被不一致地调用**。 | 声明在指针接收者上的 `MarshalJSON` 和 `UnmarshalJSON` 方法**被一致地调用**。 | [PointerReceiver](/v1/diff_test.go#:~:text=TestPointerReceiver) |
| Go 映射以**确定性顺序**编组。 | Go 映射以**非确定性顺序**编组。 | [MapDeterminism](/v1/diff_test.go#:~:text=TestMapDeterminism) |
| JSON 字符串编码时**会转义 HTML 特定字符**。 | JSON 字符串编码时**不转义任何字符**(除非必要)。 | [EscapeHTML](/v1/diff_test.go#:~:text=TestEscapeHTML) |
| 编组时,Go 字符串中的无效 UTF-8 **会被静默替换**。 | 编组时,Go 字符串中的无效 UTF-8 **会导致错误**。 | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) |
| 解组时,JSON 字符串中的无效 UTF-8 **会被静默替换**。 | 解组时,JSON 字符串中的无效 UTF-8 **会导致错误**。 | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) |
| 编组时,如果输出 JSON 值包含具有重复名称的对象,**不会发生错误**。 | 编组时,如果输出 JSON 值包含具有重复名称的对象,**会发生错误**。 | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) |
| 解组时,如果输入 JSON 值包含具有重复名称的对象,**不会发生错误**。 | 解组时,如果输入 JSON 值包含具有重复名称的对象,**会发生错误**。 | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) |
| 将 JSON null 解组到非空 Go 值时,**行为不一致,可能清除值或不执行任何操作**。 | 将 JSON null 解组到非空 Go 值时,**总是清除该值**。 | [MergeNull](/v1/diff_test.go#:~:text=TestMergeNull) |
| 将 JSON 值解组到非空 Go 值时,**遵循不一致且奇怪的行为**。 | 将 JSON 值解组到非空 Go 值时,**如果输入是对象则总是合并,否则替换**。 | [MergeComposite](/v1/diff_test.go#:~:text=TestMergeComposite) |
| `time.Duration` 表示为 **包含纳秒小数的 JSON 数字**。 | `time.Duration` 在 v2 中没有默认表示形式(参见 [#71631](https://golang.org/issue/71631)),会导致错误。 | |
| 仅包含未导出字段的 Go 结构体**可以被序列化**。 | 仅包含未导出字段的 Go 结构体**不能被序列化**。 | [EmptyStructs](/v1/diff_test.go#:~:text=TestEmptyStructs) |
关于每一项变更的详情,请参阅 [diff_test.go](/v1/diff_test.go)。
## 性能
v2 模块的目标之一是比 v1 更高效,
但不能以牺牲正确性为代价。
通常,v2 在编组性能上与 v1 持平,
但在解组方面显著更快。
参阅 https://github.com/go-json-experiment/jsonbench 获取基准测试结果,
该测试将 v2 与 v1 及其他多个流行的 JSON 实现进行了比较。
标签:API设计, Go模块, Go语言, JSON序列化, v2版本, 后端开发, 实验性模块, 序列化库, 数据格式, 日志审计, 标准库扩展, 程序破解, 编程工具, 软件开发, 远程代码执行