Go JSON v2,版权归原作者所有。
Go 1.25 中即将推出的 json
包的第二个版本是一次重大更新,包含许多破坏性变更。v2 包添加了新特性,修复了 API 问题和行为缺陷,并提升了性能。让我们来看看都有哪些变化!
使用 Marshal
和 Unmarshal
的基本用例保持不变。以下代码在 v1 和 v2 中都能正常工作:
type Person struct {
Name string
Age int
}
alice := Person{Name: "Alice", Age: 25}
// 序列化 Alice
b, err := json.Marshal(alice)
fmt.Println(string(b), err)
// 反序列化 Alice
err = json.Unmarshal(b, &alice)
fmt.Println(alice, err)
但其余部分有很大不同。让我们来看看 v1 的主要变化。
在 v1 中,你使用 Encoder
序列化到 io.Writer
,使用 Decoder
从 io.Reader
反序列化:
// 序列化 Alice
alice := Person{Name: "Alice", Age: 25}
out := new(strings.Builder) // io.Writer
enc := json.NewEncoder(out)
enc.Encode(alice)
fmt.Println(out.String())
// 反序列化 Bob
in := strings.NewReader(`{"Name":"Bob","Age":30}`) // io.Reader
dec := json.NewDecoder(in)
var bob Person
dec.Decode(&bob)
fmt.Println(bob)
从现在开始,我将省略错误处理以保持简洁。
在 v2 中,你可以直接使用 MarshalWrite
和 UnmarshalRead
,无需任何中间媒介:
// 序列化 Alice
alice := Person{Name: "Alice", Age: 25}
out := new(strings.Builder)
json.MarshalWrite(out, alice)
fmt.Println(out.String())
// 反序列化 Bob
in := strings.NewReader(`{"Name":"Bob","Age":30}`)
var bob Person
json.UnmarshalRead(in, &bob)
fmt.Println(bob)
但它们并不完全可互换:
MarshalWrite
不添加换行符,而旧版 Encoder.Encode
会添加。UnmarshalRead
从 reader 读取所有内容直到遇到 io.EOF
,而旧版 Decoder.Decode
只读取下一个 JSON 值。Encoder
和 Decoder
类型已经移至新的 jsontext
包,且它们的接口已经发生了重大变化(以支持低级流式编码/解码操作)。
你可以将它们与 json
函数一起使用,以读写 JSON 流,类似于之前 Encode
和 Decode
的工作方式:
Encoder.Encode
→ v2 json.MarshalEncode
+ jsontext.Encoder
。Decoder.Decode
→ v2 json.UnmarshalDecode
+ jsontext.Decoder
。流式编码器:
people := []Person{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Cindy", Age: 15},
}
out := new(strings.Builder)
enc := jsontext.NewEncoder(out)
for _, p := range people {
json.MarshalEncode(enc, p)
}
fmt.Print(out.String())
流式解码器:
in := strings.NewReader(`
{"Name":"Alice","Age":25}
{"Name":"Bob","Age":30}
{"Name":"Cindy","Age":15}
`)
dec := jsontext.NewDecoder(in)
for {
var p Person
// 每次调用解码一个 Person 对象
err := json.UnmarshalDecode(dec, &p)
if err == io.EOF {
break
}
fmt.Println(p)
}
与 UnmarshalRead
不同,UnmarshalDecode
以完全流式方式工作,每次调用解码一个值,而不是读取所有内容直到 io.EOF
。
选项用于配置序列化和反序列化函数的特定功能:
FormatNilMapAsNull
和 FormatNilSliceAsNull
定义如何编码 nil 映射和切片。MatchCaseInsensitiveNames
允许匹配 Name
↔ name
等。Multiline
将 JSON 对象展开为多行。OmitZeroStructFields
从输出中省略零值字段。SpaceAfterColon
和 SpaceAfterComma
在每个 :
或 ,
后添加空格。StringifyNumbers
将数字类型表示为字符串。WithIndent
和 WithIndentPrefix
缩进嵌套属性(注意 MarshalIndent
函数已被移除)。每个序列化或反序列化函数可以接受任意数量的选项:
alice := Person{Name: "Alice", Age: 25}
b, _ := json.Marshal(
alice,
json.OmitZeroStructFields(true),
json.StringifyNumbers(true),
jsontext.WithIndent(" "),
)
fmt.Println(string(b))
你也可以使用 JoinOptions
组合选项:
alice := Person{Name: "Alice", Age: 25}
opts := json.JoinOptions(
jsontext.SpaceAfterColon(true),
jsontext.SpaceAfterComma(true),
)
b, _ := json.Marshal(alice, opts)
fmt.Println(string(b))
查看文档中的完整选项列表:有些在 json
包中,有些在 jsontext
包中。
v2 支持在 v1 中定义的字段标签:
omitzero
和 omitempty
用于省略空值。string
将数字类型表示为字符串。-
用于忽略字段。并添加了一些新的标签:
case:ignore
或 case:strict
指定如何处理大小写差异。format:template
根据模板格式化字段值。inline
通过将嵌套对象的字段提升到父级来扁平化输出。unknown
为未知字段提供"全部捕获"。下面是演示 inline
和 format
的示例:
type Person struct {
Name string `json:"name"`
// 格式化日期为 yyyy-mm-dd
BirthDate time.Time `json:"birth_date,format:DateOnly"`
// 将 Address 字段内联到 Person 对象中
Address `json:",inline"`
}
type Address struct {
Street string `json:"street"`
City string `json:"city"`
}
func main() {
alice := Person{
Name: "Alice",
BirthDate: time.Date(2001, 7, 15, 12, 35, 43, 0, time.UTC),
Address: Address{
Street: "123 Main St",
City: "Wonderland",
},
}
b, _ := json.Marshal(alice, jsontext.WithIndent(" "))
fmt.Println(string(b))
}
还有 unknown
:
type Person struct {
Name string `json:"name"`
// 将所有未知的 Person 字段收集到 Data 字段中
Data map[string]any `json:",unknown"`
}
func main() {
src := `{
"name": "Alice",
"hobby": "adventure",
"friends": [
{"name": "Bob"},
{"name": "Cindy"}
]
}`
var alice Person
json.Unmarshal([]byte(src), &alice)
fmt.Println(alice)
}
使用 Marshaler
和 Unmarshaler
接口的基本自定义序列化用例保持不变。此代码在 v1 和 v2 中都能正常工作:
// 一个自定义布尔类型,表示为 "✓" 表示 true,"✗" 表示 false
type Success bool
func (s Success) MarshalJSON() ([]byte, error) {
if s {
return []byte(`"✓"`), nil
}
return []byte(`"✗"`), nil
}
func (s *Success) UnmarshalJSON(data []byte) error {
// 为简洁起见省略数据验证
*s = string(data) == `"✓"`
return nil
}
func main() {
// 序列化
val := Success(true)
data, err := json.Marshal(val)
fmt.Println(string(data), err)
// 反序列化
src := []byte(`"✓"`)
err = json.Unmarshal(src, &val)
fmt.Println(val, err)
}
然而,Go 标准库文档推荐使用新的 MarshalerTo
和 UnmarshalerFrom
接口(它们以纯流式方式工作,这可能快得多):
// 一个自定义布尔类型,表示为 "✓" 表示 true,"✗" 表示 false
type Success bool
func (s Success) MarshalJSONTo(enc *jsontext.Encoder) error {
if s {
return enc.WriteToken(jsontext.String("✓"))
}
return enc.WriteToken(jsontext.String("✗"))
}
func (s *Success) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
// 为简洁起见省略数据验证
tok, err := dec.ReadToken()
*s = tok.String() == `"✓"`
return err
}
func main() {
// 序列化
val := Success(true)
data, err := json.Marshal(val)
fmt.Println(string(data), err)
// 反序列化
src := []byte(`"✓"`)
err = json.Unmarshal(src, &val)
fmt.Println(val, err)
}
更好的是,你不再局限于特定类型的单一序列化方式。现在,你可以在需要时使用自定义序列化器和反序列化器,使用通用的 MarshalFunc
和 UnmarshalFunc
函数。
func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers
func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers
例如,你可以将 bool
值序列化为 ✓
或 ✗
而无需创建自定义类型:
// 用于布尔值的自定义序列化器
boolMarshaler := json.MarshalFunc(
func(val bool) ([]byte, error) {
if val {
return []byte(`"✓"`), nil
}
return []byte(`"✗"`), nil
},
)
// 通过 WithMarshalers 选项传递自定义序列化器给 Marshal
val := true
data, err := json.Marshal(val, json.WithMarshalers(boolMarshaler))
fmt.Println(string(data), err)
并将 ✓
或 ✗
反序列化为 bool
:
// 用于布尔值的自定义反序列化器
boolUnmarshaler := json.UnmarshalFunc(
func(data []byte, val *bool) error {
*val = string(data) == `"✓"`
return nil
},
)
// 通过 WithUnmarshalers 选项传递自定义反序列化器给 Unmarshal
src := []byte(`"✓"`)
var val bool
err := json.Unmarshal(src, &val, json.WithUnmarshalers(boolUnmarshaler))
fmt.Println(val, err)
也有 MarshalToFunc
和 UnmarshalFromFunc
函数用于创建自定义序列化器。它们类似于 MarshalFunc
和 UnmarshalFunc
,但使用 jsontext.Encoder
和 jsontext.Decoder
而不是字节切片。
func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers
func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers
你可以使用 JoinMarshalers
组合序列化器(使用 JoinUnmarshalers
组合反序列化器)。例如,这里展示了如何将布尔值(true
/false
)和"类布尔"字符串(on
/off
)序列化为 ✓
或 ✗
,同时保留所有其他值的默认序列化方式。
首先定义一个布尔值的自定义序列化器:
// 将布尔值序列化为 ✓ 或 ✗
boolMarshaler := json.MarshalToFunc(
func(enc *jsontext.Encoder, val bool) error {
if val {
return enc.WriteToken(jsontext.String("✓"))
}
return enc.WriteToken(jsontext.String("✗"))
},
)
然后定义一个类布尔字符串的自定义序列化器:
// 将类布尔字符串序列化为 ✓ 或 ✗
strMarshaler := json.MarshalToFunc(
func(enc *jsontext.Encoder, val string) error {
if val == "on" || val == "true" {
return enc.WriteToken(jsontext.String("✓"))
}
if val == "off" || val == "false" {
return enc.WriteToken(jsontext.String("✗"))
}
// SkipFunc 是一种特殊类型的错误,告诉 Go 跳过当前序列化器
// 并继续下一个。在我们的例子中,下一个将是字符串的默认序列化器。
return json.SkipFunc
},
)
最后,使用 JoinMarshalers
组合序列化器,并通过 WithMarshalers
选项将它们传递给序列化函数:
// 使用 JoinMarshalers 组合自定义序列化器
marshalers := json.JoinMarshalers(boolMarshaler, strMarshaler)
// 序列化一些值
vals := []any{true, "off", "hello"}
data, err := json.Marshal(vals, json.WithMarshalers(marshalers))
fmt.Println(string(data), err)
这不是很酷吗?
v2 不仅改变了包接口,还改变了默认的序列化/反序列化行为。
一些值得注意的序列化差异包括:
null
,v2 序列化为 []
。你可以使用 FormatNilSliceAsNull
选项更改它。null
,v2 序列化为 {}
。你可以使用 FormatNilMapAsNull
选项更改它。format:array
和 format:base64
标签更改它。AllowInvalidUTF8
选项更改它。以下是 v2 默认行为的示例:
type Person struct {
Name string
Hobbies []string
Skills map[string]int
Secret [5]byte
}
func main() {
alice := Person{
Name: "Alice",
Secret: [5]byte{1, 2, 3, 4, 5},
}
b, _ := json.Marshal(alice, jsontext.Multiline(true))
fmt.Println(string(b))
}
以下是如何强制 v1 行为:
type Person struct {
Name string
Hobbies []string
Skills map[string]int
Secret [5]byte `json:",format:array"`
}
func main() {
alice := Person{
Name: "Alice",
Secret: [5]byte{1, 2, 3, 4, 5},
}
b, _ := json.Marshal(
alice,
json.FormatNilMapAsNull(true),
json.FormatNilSliceAsNull(true),
jsontext.Multiline(true),
)
fmt.Println(string(b))
}
一些值得注意的反序列化差异包括:
MatchCaseInsensitiveNames
选项或 case
标签更改它。AllowDuplicateNames
选项更改它。以下是 v2 默认行为(区分大小写)的示例:
type Person struct {
FirstName string
LastName string
}
func main() {
src := []byte(`{"firstname":"Alice","lastname":"Zakas"}`)
var alice Person
json.Unmarshal(src, &alice)
fmt.Printf("%+v\n", alice)
}
以下是如何强制 v1 行为(不区分大小写):
type Person struct {
FirstName string
LastName string
}
func main() {
src := []byte(`{"firstname":"Alice","lastname":"Zakas"}`)
var alice Person
json.Unmarshal(
src, &alice,
json.MatchCaseInsensitiveNames(true),
)
fmt.Printf("%+v\n", alice)
}
在文档中可以查看行为变化的完整列表。
在序列化时,v2 与 v1 的表现大致相同。它对某些数据集更快,但对其他数据集更慢。然而,反序列化要好得多:v2 比 v1 快 2.7 倍到 10.2 倍。
此外,通过从常规的 MarshalJSON
和 UnmarshalJSON
切换到它们的流式替代方案 — MarshalJSONTo
和 UnmarshalJSONFrom
,可以获得显著的性能提升。根据 Go 团队的说法,它可以将某些 O(n²) 运行时场景转换为 O(n)。例如,在 k8s OpenAPI 规范中从 UnmarshalJSON
切换到 UnmarshalJSONFrom
使其速度提高了约 40 倍。
有关基准测试详情,请参阅 jsonbench 仓库。
呼!这里有太多要吸收的内容。v2 包比 v1 有更多功能且更灵活,但它也更复杂,特别是考虑到分成 json/v2
和 jsontext
子包。
需要记住的几点:
json/v2
包仍处于实验阶段,可以通过在构建时设置 GOEXPERIMENT=jsonv2
来启用。包 API 可能在未来的版本中发生变化。GOEXPERIMENT=jsonv2
会让 v1 json
包使用新的 JSON 实现,这更快并支持一些选项,以便与旧的序列化和反序列化行为更好地兼容。最后,以下是一些了解 v2 设计和实现的链接:
提案第一部分 • 提案第二部分 • json/v2 • jsontext
comments powered by Disqus2025-07-17