Go JSON v2 來了?。?!
說實(shí)話,現(xiàn)有的 encoding/json 包雖然能用,但是各種限制和性能問題確實(shí)讓人頭疼,特別是處理大量數(shù)據(jù)的時(shí)候,那速度真的是...很容易擺爛。
新版本 Go1.25 有一個(gè)很多 Gopher 期待已久的新標(biāo)準(zhǔn)庫——JSON v2[1](實(shí)驗(yàn)階段),能夠較好的解決這個(gè)煩惱。
圖片
為什么需要 JSON v2?
第一:現(xiàn)有的 encoding/json 包確實(shí)有不少歷史包袱。從 Go 1.0 到現(xiàn)在,這個(gè)包基本沒怎么大改過,很多設(shè)計(jì)上的問題一直拖著。比如說:
Encoder/Decoder的 API 設(shè)計(jì)不夠靈活。- 自定義序列化的方式太單一。
- 性能問題,特別是反序列化的性能。
- 一些默認(rèn)行為不太合理。
第二:社區(qū)呼聲很高。你去看看 GitHub 上的 issue,關(guān)于 JSON 包的吐槽那是一抓一大把。大家都希望有個(gè)更好用、更快的 JSON 包。
但很無奈的是,Go 的兼容性承諾在那擺著,不能隨便改現(xiàn)有的 API。
所以 Go 團(tuán)隊(duì)這次的思路是:搞個(gè) v2,先用 GOEXPERIMENT 來控制。
JSON v2 到底改了啥?
接下來我們看下 JSON v2 的核心變化。這個(gè)新版本不是簡單的修修補(bǔ)補(bǔ)。
是從 API 設(shè)計(jì)到內(nèi)部實(shí)現(xiàn)的全面重構(gòu)。所以搞了很久才釋出。
新的 API 設(shè)計(jì)
最明顯的變化就是 API:
圖片
以下是一些要點(diǎn)介紹。
更直觀的 IO 操作
原來我們要寫入 io.Writer 或者從 io.Reader 讀取,得用 Encoder 和 Decoder:
// v1 的寫法
alice := Person{Name: "Alice", Age: 25}
out := new(strings.Builder)
enc := json.NewEncoder(out)
enc.Encode(alice)
fmt.Println(out.String())現(xiàn)在 v2 直接提供了 MarshalWrite 和 UnmarshalRead:
// v2 的寫法
alice := Person{Name: "Alice", Age: 25}
out := new(strings.Builder)
json.MarshalWrite(out, alice)
fmt.Println(out.String())簡單來說,就是少了一層中間商賺差價(jià)。
不過這里有幾個(gè)點(diǎn)需要注意:
MarshalWrite不會添加換行符,而老的Encoder.Encode會。UnmarshalRead會讀取整個(gè) reader 直到 EOF,而老的Decoder.Decode只讀下一個(gè) JSON 值。
如果你需要流式處理,v2 也提供了新的方案。Encoder 和 Decoder 被移到了 jsontext 包,配合 MarshalEncode 和 UnmarshalDecode 使用:
// 流式編碼
people := []Person{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
}
out := new(strings.Builder)
enc := jsontext.NewEncoder(out)
for _, p := range people {
json.MarshalEncode(enc, p)
}強(qiáng)大的配置選項(xiàng)
這個(gè)真的是 v2 的一大亮點(diǎn)。再也不用為了改個(gè)格式而頭疼了。
alice := Person{Name: "Alice", Age: 25}
b, _ := json.Marshal(
alice,
json.OmitZeroStructFields(true), // 省略零值字段
json.StringifyNumbers(true), // 數(shù)字轉(zhuǎn)字符串
jsontext.WithIndent(" "), // 縮進(jìn)格式化
)
fmt.Println(string(b))還可以用 JoinOptions 組合選項(xiàng):
opts := json.JoinOptions(
jsontext.SpaceAfterColon(true),
jsontext.SpaceAfterComma(true),
)
b, _ := json.Marshal(alice, opts)增強(qiáng)的標(biāo)簽支持
v2 不僅支持原有的標(biāo)簽,還新增了幾個(gè)很實(shí)用的:
type Person struct {
Name string `json:"name"`
BirthDate time.Time `json:"birth_date,format:DateOnly"` // 格式化日期
Address `json:",inline"` // 內(nèi)聯(lián)字段
}
type Address struct {
Street string `json:"street"`
City string `json:"city"`
}inline 標(biāo)簽可以把嵌套對象的字段提升到父對象:
alice := Person{
Name: "Alice",
BirthDate: time.Date(2001, 7, 15, 12, 35, 43, 0, time.UTC),
Address: Address{
Street: "123 Main St",
City: "Wonderland",
},
}
// 輸出會把 street 和 city 直接放在 Person 對象里還有個(gè) unknown 標(biāo)簽,可以收集所有未知字段:
type Person struct {
Name string `json:"name"`
Data map[string]any `json:",unknown"` // 收集未知字段
}靈活的自定義序列化
這個(gè)功能真的讓我眼前一亮。以前要自定義序列化,必須給類型實(shí)現(xiàn) MarshalJSON 方法。
現(xiàn)在你可以用 MarshalFunc 隨時(shí)定義:
// 把 bool 序列化成 ? 或 ?
boolMarshaler := json.MarshalFunc(
func(val bool) ([]byte, error) {
if val {
return []byte(`"?"`), nil
}
return []byte(`"?"`), nil
},
)
val := true
data, err := json.Marshal(val, json.WithMarshalers(boolMarshaler))
fmt.Println(string(data)) // "?"還可以組合多個(gè)自定義序列化器:
// 可以同時(shí)處理 bool 和 bool-like 字符串
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("?"))
}
return json.SkipFunc // 跳過,使用默認(rèn)序列化
},
)
marshalers := json.JoinMarshalers(boolMarshaler, strMarshaler)默認(rèn)行為的改進(jìn)
v2 修正了一些反直覺的默認(rèn)行為:
- nil slice 現(xiàn)在序列化成
[]而不是null。 - nil map 現(xiàn)在序列化成
{}而不是null。 - 字節(jié)數(shù)組默認(rèn)編碼為 base64 字符串,而不是數(shù)字?jǐn)?shù)組。
- 字段名匹配默認(rèn)區(qū)分大小寫。
例子如下:
type Person struct {
Name string
Hobbies []string
Skills map[string]int
Secret [5]byte
}
alice := Person{
Name: "Alice",
Secret: [5]byte{1, 2, 3, 4, 5},
}
// v2 輸出:
// {
// "Name": "Alice",
// "Hobbies": [],
// "Skills": {},
// "Secret": "AQIDBAU="
// }如果想要 v1 的行為,可以通過選項(xiàng)配置:
b, _ := json.Marshal(
alice,
json.FormatNilMapAsNull(true),
json.FormatNilSliceAsNull(true),
)JSON v2 性能大幅提高
目前看來,根據(jù)官方的 benchmark[2] 數(shù)據(jù),反序列化性能提升了 2.7 到 10.2 倍,這個(gè)提升幅度確實(shí)很可觀。
圖片
特別是對于那些需要處理大量 JSON 數(shù)據(jù)的項(xiàng)目,比如微服務(wù)之間的通信、日志處理等場景,這個(gè)性能提升是實(shí)打?qū)嵉摹?/span>
如何嘗鮮?
我們來看下怎么體驗(yàn) JSON v2。目前 JSON v2 還在實(shí)驗(yàn)階段。
圖片
需要設(shè)置環(huán)境變量開啟:
GOEXPERIMENT=jsonv2 go build your_project當(dāng)設(shè)置了這個(gè)標(biāo)志后:
- 新的
encoding/json/v2和encoding/json/jsontext包會生效。 - 現(xiàn)有的
encoding/json包會使用 v2 的實(shí)現(xiàn)。 - 可以使用 v2 提案中的新 API。
總結(jié)
從目前的設(shè)計(jì)來看,JSON v2 確實(shí)解決了現(xiàn)有 encoding/json 包的很多痛點(diǎn):性能大幅提升、API 更加靈活、支持更多自定義選項(xiàng)。雖然學(xué)習(xí)成本有一定增加,但是帶來的收益是值得的。
如果能在后續(xù)版本轉(zhuǎn)正(正式落地),相信會讓很多 Gopher 的開發(fā)體驗(yàn)有質(zhì)的提升。畢竟誰不想告別那些繁瑣的 JSON 處理代碼呢?
參考資料
[1] JSON v2: https://github.com/golang/go/issues/71845
[2] benchmark: https://github.com/go-json-experiment/jsonbench






























