《Go Cookbook CN》系列 09:数据交互核心——JSON解析与序列化全解
JSON是Go语言开发中最常用的数据交换格式,尤其在RESTful API交互、配置解析等核心场景中不可或缺。本文基于《Go语言实战宝典》JSON章节内容,全面拆解Go中encoding/json包的核心使用方法,从结构化/非结构化JSON解析,到流式数据处理、序列化字段优化,学完即可掌握Go处理JSON的全场景实战能力。
【本篇核心收获】
- 掌握Go中通过
encoding/json包将JSON字节数组解析为结构体的完整流程,理解结构体标签的核心作用 - 学会处理非结构化/动态键的JSON数据,掌握
map[string]any的使用与类型断言技巧 - 区分
Unmarshal/Decode、Marshal/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的核心步骤为:
- 创建与JSON数据结构匹配的Go结构体(通过结构体标签映射字段名差异);
- 使用
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输出格式,避免无意义的默认值/空值字段。
【本篇核心知识点速记】
- JSON解析核心方案:结构化JSON用「结构体+
json.Unmarshal」,非结构化JSON用map[string]any,流式JSON用「Decoder.Decode」; - 结构体标签:
json:"key"映射JSON与Go字段名,omitempty省略空字段,是JSON解析/序列化的核心配置; - JSON序列化核心方案:一次性数据用
json.Marshal(紧凑)/json.MarshalIndent(格式化),流式数据用「Encoder.Encode」; - 性能优先级:
Decode/Encode>Unmarshal/Marshal,前者更高效、内存占用更低,适配流式场景; - 类型安全优先:解析JSON时优先使用结构体(强类型、易维护),
map[string]any仅作为动态结构的兜底方案。
