lyyyuna 的小花园

动静中之动, by

RSS

Go JSON 进化:从 v1 到 v2

发表于 2025-08

Go 1.25 带来了一个实验性的新 JSON 包 encoding/json/v2。它提供了改进的 API、更好的性能以及向后兼容的迁移路径。

要尝试新的 JSON 包,你需要:

  1. Go 1.25
  2. GOEXPERIMENT=jsonv2 环境变量

让我们探索主要功能和改进。

基本用法

基本的编码和解码操作看起来很熟悉:

package main

import (
    "fmt"
    jsonv2 "encoding/json/v2"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    // 编码
    p := Person{Name: "Alice", Age: 30}
    data, err := jsonv2.Marshal(p)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data)) // {"name":"Alice","age":30}

    // 解码
    var p2 Person
    err = jsonv2.Unmarshal(data, &p2)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", p2) // {Name:Alice Age:30}
}

MarshalWrite 和 UnmarshalRead

新包引入了流式接口,允许直接向/从 io.Writerio.Reader 进行编码/解码:

import (
    "bytes"
    "strings"
    jsonv2 "encoding/json/v2"
)

func main() {
    p := Person{Name: "Bob", Age: 25}
    
    // 写入缓冲区
    var buf bytes.Buffer
    err := jsonv2.MarshalWrite(&buf, p, nil)
    if err != nil {
        panic(err)
    }
    
    // 从 reader 读取
    reader := strings.NewReader(buf.String())
    var p2 Person
    err = jsonv2.UnmarshalRead(reader, &p2, nil)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("%+v\n", p2) // {Name:Bob Age:25}
}

MarshalEncode 和 UnmarshalDecode

对于更复杂的场景,新包提供了 EncoderDecoder 类型:

import (
    "bytes"
    "strings"
    jsonv2 "encoding/json/v2"
)

func main() {
    // 使用编码器
    var buf bytes.Buffer
    enc := jsonv2.NewEncoder(&buf)
    
    people := []Person{
        {Name: "Alice", Age: 30},
        {Name: "Bob", Age: 25},
    }
    
    for _, p := range people {
        err := enc.Encode(p)
        if err != nil {
            panic(err)
        }
    }
    
    // 使用解码器
    dec := jsonv2.NewDecoder(strings.NewReader(buf.String()))
    
    for {
        var p Person
        err := dec.Decode(&p)
        if err != nil {
            break // EOF 或其他错误
        }
        fmt.Printf("%+v\n", p)
    }
}

选项

新包引入了灵活的选项系统来自定义编码/解码行为:

import jsonv2 "encoding/json/v2"

func main() {
    p := Person{Name: "Charlie", Age: 35}
    
    // 使用选项进行美观打印
    data, err := jsonv2.Marshal(p, jsonv2.WithIndent("", "  "))
    if err != nil {
        panic(err)
    }
    
    fmt.Println(string(data))
    // {
    //   "name": "Charlie",
    //   "age": 35
    // }
    
    // 严格解码 - 拒绝未知字段
    jsonStr := `{"name":"David","age":40,"unknown":"field"}`
    var p2 Person
    err = jsonv2.Unmarshal([]byte(jsonStr), &p2, jsonv2.WithRejectUnknownMembers(true))
    if err != nil {
        fmt.Printf("错误: %v\n", err) // 错误: 未知字段 "unknown"
    }
}

标签

v2 包增强了对结构体标签的支持:

type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price,omitempty"`
    Tags  []string `json:"tags,omitzero"`
}

func main() {
    // omitempty: 省略空值
    p1 := Product{ID: 1, Name: "Widget"}
    data1, _ := jsonv2.Marshal(p1)
    fmt.Println(string(data1)) // {"id":1,"name":"Widget"}
    
    // omitzero: 省略零值
    p2 := Product{ID: 2, Name: "Gadget", Tags: []string{}}
    data2, _ := jsonv2.Marshal(p2)
    fmt.Println(string(data2)) // {"id":2,"name":"Gadget"}
}

自定义编组

新包为自定义编组提供了改进的接口:

import (
    "time"
    jsonv2 "encoding/json/v2"
)

type CustomTime struct {
    time.Time
}

func (ct CustomTime) MarshalJSONV2(enc *jsonv2.Encoder, opts jsonv2.Options) error {
    return enc.WriteString(ct.Format("2006-01-02"))
}

func (ct *CustomTime) UnmarshalJSONV2(dec *jsonv2.Decoder, opts jsonv2.Options) error {
    str, err := dec.ReadString()
    if err != nil {
        return err
    }
    
    t, err := time.Parse("2006-01-02", str)
    if err != nil {
        return err
    }
    
    ct.Time = t
    return nil
}

type Event struct {
    Name string     `json:"name"`
    Date CustomTime `json:"date"`
}

func main() {
    e := Event{
        Name: "Meeting",
        Date: CustomTime{time.Date(2025, 6, 22, 0, 0, 0, 0, time.UTC)},
    }
    
    data, err := jsonv2.Marshal(e)
    if err != nil {
        panic(err)
    }
    
    fmt.Println(string(data)) // {"name":"Meeting","date":"2025-06-22"}
}

默认行为

v2 包改变了一些默认行为:

type Config struct {
    HTML string `json:"html"`
}

func main() {
    c := Config{HTML: "<script>alert('hello')</script>"}
    
    // v1 会转义 < > &
    // v2 默认不转义
    data, _ := jsonv2.Marshal(c)
    fmt.Println(string(data)) // {"html":"<script>alert('hello')</script>"}
    
    // 如果需要转义,使用选项
    dataEscaped, _ := jsonv2.Marshal(c, jsonv2.WithEscapeHTML(true))
    fmt.Println(string(dataEscaped)) // {"html":"\u003cscript\u003ealert('hello')\u003c/script\u003e"}
}

性能

新包提供了显著的性能改进,特别是在反编组操作方面:

迁移

从 v1 迁移到 v2 通常很简单:

  1. 将导入从 "encoding/json" 更改为 jsonv2 "encoding/json/v2"
  2. 如果有的话,更新自定义编组接口
  3. 测试并调整任何依赖于更改的默认行为的代码

新包设计为大多数现有代码的直接替代品,只需要最少的更改。

结论

Go 的新 JSON v2 包代表了 JSON 处理的重大改进,提供了:

虽然仍然是实验性的,但 JSON v2 有望成为 Go 生态系统中 JSON 处理的新标准。随着 Go 1.25 发布,现在是探索这些新功能并为迁移做准备的绝佳时机。

lyyyuna 沪ICP备2025110782号-1