Skip to content

《Go Cookbook CN》系列 09:数据交互核心——JSON解析与序列化全解

约 4192 字大约 14 分钟

《Go Cookbook CN》系列Go语言

2026-04-02

JSON是Go语言开发中最常用的数据交换格式,尤其在RESTful API交互、配置解析等核心场景中不可或缺。本文基于《Go语言实战宝典》JSON章节内容,全面拆解Go中encoding/json包的核心使用方法,从结构化/非结构化JSON解析,到流式数据处理、序列化字段优化,学完即可掌握Go处理JSON的全场景实战能力。

【本篇核心收获】

  • 掌握Go中通过encoding/json包将JSON字节数组解析为结构体的完整流程,理解结构体标签的核心作用
  • 学会处理非结构化/动态键的JSON数据,掌握map[string]any的使用与类型断言技巧
  • 区分Unmarshal/DecodeMarshal/Encode的适用场景,熟练处理JSON流式数据
  • 掌握JSON序列化时省略空字段的方法,通过omitempty标签优化JSON输出格式
  • 理解不同JSON处理方式的性能差异,能根据场景选择最优的JSON处理方案

9.0 JSON核心概述

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人类阅读和编写,同时也易于机器解析和生成。它基于JavaScript的一个子集,最初由Douglas Crockford定义,目前由RFC 7159与ECMA-404规范描述。

JSON的核心应用场景包括:

  • RESTful Web服务的数据交互(最主流场景);
  • 配置文件的存储与解析;
  • 第三方服务身份验证、跨服务控制等。

Go语言在标准库中通过encoding/json包提供了对JSON的全场景支持,涵盖解析(反序列化)、序列化、流式数据处理等所有核心能力。

模块小结:JSON是Go开发中核心的数据交换格式,encoding/json包是处理JSON的基础工具,后续所有解析与序列化操作均基于该包展开。

9.1 将JSON字节数组解析为结构体

9.1.1 核心思路

解析结构化JSON的核心步骤为:

  1. 创建与JSON数据结构匹配的Go结构体(通过结构体标签映射字段名差异);
  2. 使用json.Unmarshal函数将JSON字节数组反序列化到结构体实例中。

9.1.2 实战案例:解析星球大战角色JSON

以SWAPI(《星球大战》API)的Luke Skywalker数据为例,先定义JSON文件(skywalker.json):

{
  "name": "Luke Skywalker",
  "height": "172",
  "mass": "77",
  "hair_color": "blond",
  "skin_color": "fair",
  "eye_color": "blue",
  "birth_year": "19BBY",
  "gender": "male",
  "homeworld": "https://swapi.dev/api/planets/1/",
  "films": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/6/"
  ],
  "species": [],
  "vehicles": [
    "https://swapi.dev/api/vehicles/14/",
    "https://swapi.dev/api/vehicles/30/"
  ],
  "starships": [
    "https://swapi.dev/api/starships/12/",
    "https://swapi.dev/api/starships/22/"
  ],
  "created": "2014-12-09T13:50:51.644000Z",
  "edited": "2014-12-20T21:17:56.891000Z",
  "url": "https://swapi.dev/api/people/1/"
}

针对上述JSON,定义匹配的Go结构体:

type Person struct {
    Name      string    `json:"name"`
    Height    string    `json:"height"`
    Mass      string    `json:"mass"`
    HairColor string    `json:"hair_color"` // 映射蛇形命名的JSON字段
    SkinColor string    `json:"skin_color"`
    EyeColor  string    `json:"eye_color"`
    BirthYear string    `json:"birth_year"`
    Gender    string    `json:"gender"`
    Homeworld string    `json:"homeworld"`
    Films     []string  `json:"films"`       // 切片存储JSON数组
    Species   []string  `json:"species"`
    Vehicles  []string  `json:"vehicles"`
    Starships []string  `json:"starships"`
    Created   time.Time `json:"created"`    // 支持复杂类型(时间)
    Edited    time.Time `json:"edited"`
    URL       string    `json:"url"`
}

关键说明

  • 结构体标签json:"key"用于映射JSON的蛇形命名(小写+下划线)与Go的驼峰命名(大写分隔);
  • JSON数组可通过Go的字符串切片([]string)存储,支持动态扩容;
  • 支持time.Time等复杂类型,也可使用map[string]string(仅支持string键)。

9.1.3 代码实现:文件/API读取并解析

从文件解析JSON

func unmarshal() (person Person) {
    // 打开JSON文件
    file, err := os.Open("skywalker.json")
    if err != nil {
        log.Println("Error opening json file:", err)
    }
    defer file.Close() // 延迟关闭文件
    
    // 读取文件字节数据
    data, err := io.ReadAll(file)
    if err != nil {
        log.Println("Error reading json data:", err)
    }
    
    // 反序列化到结构体
    err = json.Unmarshal(data, &person)
    if err != nil {
        log.Println("Error unmarshalling json data:", err)
    }
    return
}

从API接口解析JSON

func unmarshalAPI() (person Person) {
    // 从SWAPI接口获取JSON数据
    r, err := http.Get("https://swapi.dev/api/people/1")
    if err != nil {
        log.Println("Cannot get from URL", err)
    }
    defer r.Body.Close()
    
    // 读取响应体数据
    data, err := io.ReadAll(r.Body)
    if err != nil {
        log.Println("Error reading json data:", err)
    }
    
    // 反序列化到结构体
    err = json.Unmarshal(data, &person)
    if err != nil {
        log.Println("Error unmarshalling json data:", err)
    }
    return
}

解析后结构体输出(格式化):

json.Person{
    Name:      "Luke Skywalker",
    Height:    "172",
    Mass:      "77",
    HairColor: "blond",
    SkinColor: "fair",
    EyeColor:  "blue",
    BirthYear: "19BBY",
    Gender:    "male",
    Homeworld: "https://swapi.dev/api/planets/1/",
    Films:     {"https://swapi.dev/api/films/1/", "https://swapi.dev/api/films/2/", "https://swapi.dev/api/films/3/", "https://swapi.dev/api/films/6/"},
    Species:   {},
    Vehicles:  {"https://swapi.dev/api/vehicles/14/", "https://swapi.dev/api/vehicles/30/"},
    Starships: {"https://swapi.dev/api/starships/12/", "https://swapi.dev/api/starships/22/"},
    Created:   time.Date(2014, time.December, 9, 13, 50, 51, 644000000, time.UTC),
    Edited:    time.Date(2014, time.December, 20, 21, 17, 56, 891000000, time.UTC),
    URL:       "https://swapi.dev/api/people/1/",
}

模块小结:解析结构化JSON的核心是定义匹配的结构体(通过标签解决命名差异),利用json.Unmarshal完成字节数组到结构体的反序列化,支持本地文件和远程API两种数据源。

9.2 解析非结构化JSON数据

9.2.1 适用场景

当JSON结构未知、键名动态变化(无固定规则),或无足够文档定义结构体时,无法使用固定结构体解析,需改用map[string]any(Go 1.18前为map[string]interface{})。

9.2.2 实战案例:动态键JSON解析

示例JSON(星球大战角色-关联电影映射,键为角色名,动态变化):

{
  "Luke Skywalker": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/6/"
  ],
  "C-3PO": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/4/",
    "https://swapi.dev/api/films/5/",
    "https://swapi.dev/api/films/6/"
  ],
  "R2D2": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/4/",
    "https://swapi.dev/api/films/5/",
    "https://swapi.dev/api/films/6/"
  ],
  "Darth Vader": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/6/"
  ]
}

解析代码:

func unstructured() (output map[string]any) {
    // 打开非结构化JSON文件
    file, err := os.Open("unstructured.json")
    if err != nil {
        log.Println("Error opening json file:", err)
    }
    defer file.Close()
    
    // 读取文件数据
    data, err := io.ReadAll(file)
    if err != nil {
        log.Println("Error reading json data:", err)
    }
    
    // 反序列化到map[string]any
    err = json.Unmarshal(data, &output)
    if err != nil {
        log.Println("Error unmarshalling json data:", err)
    }
    return
}

解析后输出:

map[string]any{
    "C-3PO": []any{
        "https://swapi.dev/api/films/1/",
        "https://swapi.dev/api/films/2/",
        "https://swapi.dev/api/films/3/",
        "https://swapi.dev/api/films/4/",
        "https://swapi.dev/api/films/5/",
        "https://swapi.dev/api/films/6/",
    },
    "Darth Vader": []any{
        "https://swapi.dev/api/films/1/",
        "https://swapi.dev/api/films/2/",
        "https://swapi.dev/api/films/3/",
        "https://swapi.dev/api/films/6/",
    },
    "Luke Skywalker": []any{
        "https://swapi.dev/api/films/1/",
        "https://swapi.dev/api/films/2/",
        "https://swapi.dev/api/films/3/",
        "https://swapi.dev/api/films/6/",
    },
    "R2D2": []any{
        "https://swapi.dev/api/films/1/",
        "https://swapi.dev/api/films/2/",
        "https://swapi.dev/api/films/3/",
        "https://swapi.dev/api/films/4/",
        "https://swapi.dev/api/films/5/",
        "https://swapi.dev/api/films/6/",
    },
}

9.2.3 结构体 vs map[string]any:优劣对比

特性结构体map[string]any
类型安全强类型,编译期校验弱类型,无编译期校验
数据检索直接通过字段名,易操作需类型断言,操作复杂
错误捕获可捕获字段类型/缺失错误忽略错误,运行时可能panic
灵活性低(固定结构)高(适配动态结构)
性能较低(类型断言开销)

关键注意点:从map[string]any获取数据时,必须先做类型断言,否则会报错:

// 错误示例:any类型不支持索引
unstruct := unstructured()
vader := unstruct["Darth Vader"]
first := vader[0] // 报错:invalid operation: vader[0] (type any does not support indexing)

// 正确示例:类型断言
unstruct := unstructured()
vader, ok := unstruct["Darth Vader"].([]any) // 断言为any切片
if !ok {
    log.Println("Cannot type assert")
}
first := vader[0] // 正常获取第一个元素

使用建议:优先使用结构体解析JSON;仅当结构体不可行(动态键、结构未知)时,才用map[string]any作为最后手段。

模块小结:非结构化JSON解析依赖map[string]any,虽适配动态结构但缺乏类型安全,需通过类型断言操作数据,仅在结构体方案不可行时使用。

9.3 将JSON数据流解析为结构体

9.3.1 数据流与普通JSON的区别

  • 普通JSON:单个JSON对象/数组,可一次性读取所有数据(如包含3个角色的JSON数组);
  • JSON数据流:连续的多个JSON对象(无数组包裹),非标准JSON格式,无法通过Unmarshal一次性解析(会报invalid character '{' after top-level value错误)。

示例对比:

普通JSON(数组)JSON数据流(连续对象)
[{"name":"Luke"},{"name":"C-3PO"}]

9.3.2 核心方案:Decoder的Decode方法

针对流式JSON,需使用json.NewDecoder创建解码器,通过Decode方法逐对象解析:

// 解码流式JSON,通过通道输出结果
func decode(p chan Person) {
    // 打开流式JSON文件
    file, err := os.Open("people_STREAM.json")
    if err != nil {
        log.Println("Error opening json file:", err)
    }
    defer file.Close()
    
    // 创建JSON解码器
    decoder := json.NewDecoder(file)
    
    // 循环解码每个JSON对象
    for {
        var person Person
        err = decoder.Decode(&person) // 逐对象解码
        
        if err == io.EOF { // 到达文件末尾,退出循环
            break
        }
        if err != nil { // 解码错误,退出循环
            log.Println("Error decoding json data:", err)
            break
        }
        
        p <- person // 将解析结果发送到通道
    }
    close(p) // 关闭通道
}

// 主函数消费通道数据
func main() {
    p := make(chan Person) // 创建Person类型通道
    go decode(p)           // 启动goroutine解码
    
    // 遍历通道输出结果
    for {
        person, ok := <-p
        if ok {
            fmt.Printf("%#v\n", person)
        } else {
            break
        }
    }
}

解析输出(逐对象打印Person结构体):

json.Person{
    Name:      "Luke Skywalker",
    Height:    "172",
    Mass:      "77",
    HairColor: "blond",
    SkinColor: "fair",
    EyeColor:  "blue",
    BirthYear: "19BBY",
    Gender:    "male",
    Homeworld: "",
    Films:     nil,
    Species:   nil,
    Vehicles:  nil,
    Starships: nil,
    Created:   time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    Edited:    time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    URL:       "",
}
json.Person{
    Name:      "C-3PO",
    Height:    "167",
    Mass:      "75",
    HairColor: "n/a",
    SkinColor: "gold",
    EyeColor:  "yellow",
    BirthYear: "112BBY",
    Gender:    "n/a",
    Homeworld: "",
    Films:     nil,
    Species:   nil,
    Vehicles:  nil,
    Starships: nil,
    Created:   time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    Edited:    time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    URL:       "",
}
json.Person{
    Name:      "R2-D2",
    Height:    "96",
    Mass:      "32",
    HairColor: "n/a",
    SkinColor: "white, blue",
    EyeColor:  "red",
    BirthYear: "33BBY",
    Gender:    "n/a",
    Homeworld: "",
    Films:     nil,
    Species:   nil,
    Vehicles:  nil,
    Starships: nil,
    Created:   time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    Edited:    time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    URL:       "",
}

9.3.3 Unmarshal vs Decode:性能与场景对比

基准测试代码

var luke []byte = []byte(`{
  "name": "Luke Skywalker",
  "height": "172",
  "mass": "77",
  "hair_color": "blond",
  "skin_color": "fair",
  "eye_color": "blue",
  "birth_year": "19BBY",
  "gender": "male"
}`)

// 测试Unmarshal性能
func BenchmarkUnmarshal(b *testing.B) {
    var person Person
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        json.Unmarshal(luke, &person)
    }
}

// 测试Decode性能
func BenchmarkDecode(b *testing.B) {
    var person Person
    data := bytes.NewReader(luke)
    decoder := json.NewDecoder(data)
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        decoder.Decode(&person)
        data.Seek(0, 0) // 重置读取器位置
    }
}

测试结果

goos: darwin
goarch: arm64
pkg: 
BenchmarkUnmarshal-8   437274   2494 ns/op   272 B/op   12 allocs/op
BenchmarkDecode-8      486051   2368 ns/op    48 B/op    8 allocs/op

核心结论

方法速度内存占用适用场景
Unmarshal较慢较高(272B/op)单个JSON对象/数组,一次性读取
Decode较快较低(48B/op)流式JSON数据、细粒度操作

模块小结:流式JSON需通过Decoder.Decode逐对象解析,相比Unmarshal更灵活、性能更优,适配非一次性加载的JSON数据场景(如HTTP响应流、大文件分片)。

9.4 从结构体创建JSON字节数组

9.4.1 核心方法:Marshal/MarshalIndent

序列化是解析的反向操作,核心函数:

  • json.Marshal:将结构体转为紧凑的JSON字节数组(无换行、缩进);
  • json.MarshalIndent:生成格式化的JSON(带前缀、缩进,易读)。

9.4.2 实战案例:结构体序列化为JSON文件

步骤1:从API获取Person结构体

func get(url string) Person {
    // 调用SWAPI接口
    r, err := http.Get(url)
    if err != nil {
        log.Println("Cannot get from URL", err)
    }
    defer r.Body.Close()
    
    // 读取响应体
    data, err := io.ReadAll(r.Body)
    if err != nil {
        log.Println("Error reading json data:", err)
    }
    
    // 反序列化为Person结构体
    var person Person
    json.Unmarshal(data, &person)
    return person
}

步骤2:序列化并写入文件

func main() {
    // 获取Han Solo的Person结构体
    person := get("https://swapi.dev/api/people/14")
    
    // 方案1:生成紧凑JSON
    data, err := json.Marshal(&person)
    if err != nil {
        log.Println("Cannot marshal person:", err)
    }
    // 写入文件
    err = os.WriteFile("han.json", data, 0644)
    if err != nil {
        log.Println("Cannot write to file", err)
    }
    
    // 方案2:生成格式化JSON(易读)
    dataIndent, err := json.MarshalIndent(&person, "", "  ")
    if err != nil {
        log.Println("Cannot marshal person with indent:", err)
    }
    err = os.WriteFile("han_indent.json", dataIndent, 0644)
    if err != nil {
        log.Println("Cannot write to file", err)
    }
}

输出对比

方法输出效果
Marshal紧凑字符串:{"name":"Han Solo","height":"180",...}(无换行/缩进)
MarshalIndent格式化JSON(带缩进、换行),示例如下:
{
  "name": "Han Solo",
  "height": "180",
  "mass": "80",
  "hair_color": "brown",
  "skin_color": "fair",
  "eye_color": "brown",
  "birth_year": "29BBY",
  "gender": "male",
  "homeworld": "https://swapi.dev/api/planets/22/",
  "films": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/"
  ],
  "species": [],
  "vehicles": [],
  "starships": [
    "https://swapi.dev/api/starships/10/",
    "https://swapi.dev/api/starships/22/"
  ],
  "created": "2014-12-10T16:49:14.582Z",
  "edited": "2014-12-20T21:17:50.334Z",
  "url": "https://swapi.dev/api/people/14/"
}

模块小结json.Marshal可将结构体转为紧凑JSON字节数组,json.MarshalIndent生成易读的格式化JSON,适用于一次性序列化整结构体并写入文件/存储的场景。

9.5 从结构体创建JSON数据流

9.5.1 核心方案:Encoder的Encode方法

当需要逐对象输出JSON数据流(而非一次性生成完整JSON)时,需使用json.NewEncoder创建编码器,结合io.Writer(文件、标准输出、网络流等),通过Encode方法逐对象编码。

9.5.2 实战案例:输出JSON数据流到标准输出

步骤1:封装API获取函数

func get(n int) (person Person) {
    // 拼接API URL(按ID获取角色)
    url := "https://swapi.dev/api/people/" + strconv.Itoa(n)
    r, err := http.Get(url)
    if err != nil {
        log.Println("Cannot get from URL", err)
    }
    defer r.Body.Close()
    
    // 读取并反序列化为Person
    data, err := io.ReadAll(r.Body)
    if err != nil {
        log.Println("Error reading json data:", err)
    }
    json.Unmarshal(data, &person)
    return
}

步骤2:编码为JSON数据流

func main() {
    // 创建编码器,输出到标准输出(os.Stdout实现io.Writer)
    encoder := json.NewEncoder(os.Stdout)
    // 美化输出(设置缩进)
    encoder.SetIndent("", "  ")
    
    // 循环获取3个角色,逐对象编码为数据流
    for i := 1; i < 4; i++ {
        person := get(i)
        encoder.Encode(person) // 逐对象输出JSON
    }
}

输出结果(标准输出逐行打印JSON对象)

{
  "name": "Luke Skywalker",
  "height": "172",
  "mass": "77",
  "hair_color": "blond",
  "skin_color": "fair",
  "eye_color": "blue",
  "birth_year": "19BBY",
  "gender": "male",
  "homeworld": "https://swapi.dev/api/planets/1/",
  "films": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/6/"
  ],
  "species": [],
  "vehicles": [
    "https://swapi.dev/api/vehicles/14/",
    "https://swapi.dev/api/vehicles/30/"
  ],
  "starships": [
    "https://swapi.dev/api/starships/12/",
    "https://swapi.dev/api/starships/22/"
  ],
  "created": "2014-12-09T13:50:51.644Z",
  "edited": "2014-12-20T21:17:56.891Z",
  "url": "https://swapi.dev/api/people/1/"
}
{
  "name": "C-3PO",
  "height": "167",
  "mass": "75",
  "hair_color": "n/a",
  "skin_color": "gold",
  "eye_color": "yellow",
  "birth_year": "112BBY",
  "gender": "n/a",
  "homeworld": "https://swapi.dev/api/planets/1/",
  "films": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/4/",
    "https://swapi.dev/api/films/5/",
    "https://swapi.dev/api/films/6/"
  ],
  "species": [
    "https://swapi.dev/api/species/2/"
  ],
  "vehicles": [],
  "starships": [],
  "created": "2014-12-10T15:10:51.357Z",
  "edited": "2014-12-20T21:17:50.309Z",
  "url": "https://swapi.dev/api/people/2/"
}
{
  "name": "R2-D2",
  "height": "96",
  "mass": "32",
  "hair_color": "n/a",
  "skin_color": "white, blue",
  "eye_color": "red",
  "birth_year": "33BBY",
  "gender": "n/a",
  "homeworld": "https://swapi.dev/api/planets/8/",
  "films": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/2/",
    "https://swapi.dev/api/films/3/",
    "https://swapi.dev/api/films/4/",
    "https://swapi.dev/api/films/5/",
    "https://swapi.dev/api/films/6/"
  ],
  "species": [
    "https://swapi.dev/api/species/2/"
  ],
  "vehicles": [],
  "starships": [],
  "created": "2014-12-10T15:11:50.376Z",
  "edited": "2014-12-20T21:17:50.311Z",
  "url": "https://swapi.dev/api/people/3/"
}

9.5.3 Marshal vs Encode:性能与场景对比

基准测试代码

var jsonBytes []byte = []byte(`{"name":"Luke Skywalker","height":"172","mass":"77","hair_color":"blond","skin_color":"fair","eye_color":"blue","birth_year":"19BBY","gender":"male","homeworld":"https://swapi.dev/api/planets/1/","films":["https://swapi.dev/api/films/1/","https://swapi.dev/api/films/2/","https://swapi.dev/api/films/3/","https://swapi.dev/api/films/6/"],"species":[],"vehicles":["https://swapi.dev/api/vehicles/14/","https://swapi.dev/api/vehicles/30/"],"starships":["https://swapi.dev/api/starships/12/","https://swapi.dev/api/starships/22/"],"created":"2014-12-09T13:50:51.644Z","edited":"2014-12-20T21:17:56.891Z","url":"https://swapi.dev/api/people/1/"}`)

var person Person

// 测试Marshal性能
func BenchmarkMarshal(b *testing.B) {
    json.Unmarshal(jsonBytes, &person)
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        data, _ := json.Marshal(person)
        io.Discard.Write(data)
    }
}

// 测试Encode性能
func BenchmarkEncoder(b *testing.B) {
    json.Unmarshal(jsonBytes, &person)
    b.ResetTimer()
    
    encoder := json.NewEncoder(io.Discard)
    for i := 0; i < b.N; i++ {
        encoder.Encode(person)
    }
}

测试结果

goos: darwin
goarch: arm64
pkg: 
BenchmarkMarshal-10   4717722   236.2 ns/op   504 B/op   3 allocs/op
BenchmarkEncoder-10   5885935   203.4 ns/op   264 B/op   2 allocs/op

核心结论

方法速度内存占用适用场景
Marshal较慢较高(504B/op)全数据就绪,一次性序列化
Encode较快较低(264B/op)流式输出、实时编码(数据逐批到达)

模块小结Encoder.Encode适用于JSON数据流生成,相比Marshal更高效,支持将结构体实时编码为流式JSON输出到任意io.Writer(文件、标准输出、网络流等)。

9.6 在结构体中省略字段

9.6.1 核心方案:omitempty标签

当结构体字段值为空(如空切片、默认值)时,序列化后会输出空字段(如"species":[]),可通过omitempty标签在序列化时省略这些空字段,优化JSON输出。

9.6.2 实战案例:省略空字段序列化

步骤1:修改结构体标签

type Person struct {
    Name      string    `json:"name"`
    Height    string    `json:"height"`
    Mass      string    `json:"mass"`
    HairColor string    `json:"hair_color"`
    SkinColor string    `json:"skin_color"`
    EyeColor  string    `json:"eye_color"`
    BirthYear string    `json:"birth_year"`
    Gender    string    `json:"gender"`
    Homeworld string    `json:"homeworld"`
    Films     []string  `json:"films"`
    Species   []string  `json:"species,omitempty"`  // 省略空切片
    Vehicles  []string  `json:"vehicles,omitempty"` // 省略空切片
    Starships []string  `json:"starships,omitempty"`// 省略空切片
    Created   time.Time `json:"created"`
    Edited    time.Time `json:"edited"`
    URL       string    `json:"url"`
}

步骤2:序列化输出对比

无omitempty有omitempty
包含"species":[]等空字段省略空字段,输出更精简

示例输出(有omitempty):

{
  "name": "Owen Lars",
  "height": "178",
  "mass": "120",
  "hair_color": "brown, grey",
  "skin_color": "light",
  "eye_color": "blue",
  "birth_year": "52BBY",
  "gender": "male",
  "homeworld": "https://swapi.dev/api/planets/1/",
  "films": [
    "https://swapi.dev/api/films/1/",
    "https://swapi.dev/api/films/5/",
    "https://swapi.dev/api/films/6/"
  ],
  "created": "2014-12-10T15:52:14.024Z",
  "edited": "2014-12-20T21:17:50.317Z",
  "url": "https://swapi.dev/api/people/6/"
}

使用场景

  • 避免空字段(如空数组、0值整数)干扰JSON解析方;
  • 精简JSON输出,减少数据传输体积;
  • 区分“字段值为默认值”和“字段无数据”(如0值整数可能是真实值,也可能是未赋值,省略更合理)。

模块小结:通过在结构体标签中添加omitempty,可在序列化时自动省略空字段,优化JSON输出格式,避免无意义的默认值/空值字段。

【本篇核心知识点速记】

  1. JSON解析核心方案:结构化JSON用「结构体+json.Unmarshal」,非结构化JSON用map[string]any,流式JSON用「Decoder.Decode」;
  2. 结构体标签:json:"key"映射JSON与Go字段名,omitempty省略空字段,是JSON解析/序列化的核心配置;
  3. JSON序列化核心方案:一次性数据用json.Marshal(紧凑)/json.MarshalIndent(格式化),流式数据用「Encoder.Encode」;
  4. 性能优先级:Decode/Encode > Unmarshal/Marshal,前者更高效、内存占用更低,适配流式场景;
  5. 类型安全优先:解析JSON时优先使用结构体(强类型、易维护),map[string]any仅作为动态结构的兜底方案。