Go 语言 JSON 序列化/反序列化:Tag 用法完全指南
在 Go 语言处理 JSON 时,struct tag(结构体标签) 是核心工具,它能精准控制结构体与 JSON 之间的序列化(Go → JSON)和反序列化(JSON → Go)规则,解决字段名映射、忽略字段、空值处理、嵌套结构等常见问题。
本文从基础到实战,全面讲解 Go JSON tag 的用法、常用参数和最佳实践,每个代码示例都附带运行结果 + 通俗解释,新手也能直接看懂。
一、什么是 Go JSON Tag?
JSON tag 是结构体字段后方反引号``包裹的元信息 ,标准库 encoding/json 会解析这个标签,决定 JSON 与结构体的转换规则。
基础格式:
go
FieldName Type `json:"key_name,option"`
key_name:JSON 中的键名(自定义映射)option:可选参数(控制序列化/反序列化行为)
二、最核心:字段名映射(默认规则 vs 自定义)
1. 默认规则(无 tag)
Go 结构体字段必须大写(导出) ,否则 json 包无法访问;
默认会将结构体字段名原样输出为 JSON 键。
go
package main
import (
"encoding/json"
"fmt"
)
// 无 tag 示例
type User struct {
UserName string // JSON: "UserName"
Age int // JSON: "Age"
}
func main() {
u := User{UserName: "张三", Age: 20}
bytes, _ := json.Marshal(u)
fmt.Println(string(bytes))
}
✅ 运行结果:
{"UserName":"张三","Age":20}
✅ 结果解释:
- 没有写 tag,JSON 键名完全和结构体字段名一致;
UserName→ JSONUserName,Age→ JSONAge;- 结构体赋值的内容,原样转为 JSON 键值对。
2. 自定义 JSON 键名(最常用 Tag)
使用 json:"自定义名称" 强制指定 JSON 键,解决前后端字段命名不一致问题。
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserName string `json:"username"` // JSON 键:username
Age int `json:"user_age"` // JSON 键:user_age
}
func main() {
u := User{UserName: "张三", Age: 20}
bytes, _ := json.Marshal(u)
fmt.Println(string(bytes))
}
✅ 运行结果:
{"username":"张三","user_age":20}
✅ 结果解释:
- tag 生效了,结构体字段名被替换成我们指定的名称;
UserName→username,Age→user_age;- 这是前后端对接最常用的功能,统一命名规范。
三、JSON Tag 常用可选参数(高频实用)
1. omitempty:忽略空值
序列化时,字段为零值(空字符串、0、false、nil 切片/map),不输出到 JSON。
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserName string `json:"username,omitempty"`
Age int `json:"age,omitempty"`
}
func main() {
u := User{UserName: ""} // 空字符串 + 0
bytes, _ := json.Marshal(u)
fmt.Println(string(bytes))
}
✅ 运行结果:
{}
✅ 结果解释:
UserName是空字符串,Age是默认值 0,都属于空值;omitempty作用:空值不输出到 JSON;- 最终 JSON 为空对象
{},精简了无用数据。
2. -:完全忽略字段
序列化不输出 ,反序列化不读取,用于隐藏密码等敏感字段。
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserName string `json:"username"`
Password string `json:"-"` // 永远不参与 JSON 转换
}
func main() {
u := User{UserName: "张三", Password: "123456"}
bytes, _ := json.Marshal(u)
fmt.Println(string(bytes))
}
✅ 运行结果:
{"username":"张三"}
✅ 结果解释:
- 结构体有
Password字段,且赋值了123456; json:"-"作用:无论值是什么,都不转成 JSON;- 最终结果没有密码,保护敏感信息。
3. string:强制类型转换
将 Go 数字/布尔型,转为 JSON 字符串类型(适配前端类型要求)。
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Age int `json:"age,string"` // Go int → JSON "20"
Flag bool `json:"flag,string"` // Go bool → JSON "true"
}
func main() {
u := User{Age: 20, Flag: true}
bytes, _ := json.Marshal(u)
fmt.Println(string(bytes))
}
✅ 运行结果:
{"age":"20","flag":"true"}
✅ 结果解释:
- Go 里
Age是数字20,string强制转成 JSON 字符串"20"; - Go 里
Flag是布尔值true,转成 JSON 字符串"true"; - 解决前后端类型不匹配的问题。
四、进阶:嵌套结构体与 Tag 配合
1. 普通嵌套
结构体套结构体,默认转成 JSON 嵌套对象。
go
package main
import (
"encoding/json"
"fmt"
)
type Profile struct {
Address string `json:"address"`
}
type User struct {
Name string `json:"username"`
Profile Profile `json:"profile"` // 嵌套对象
}
func main() {
u := User{Name: "张三", Profile: Profile{Address: "北京"}}
bytes, _ := json.Marshal(u)
fmt.Println(string(bytes))
}
✅ 运行结果:
{"username":"张三","profile":{"address":"北京"}}
✅ 结果解释:
- 结构体嵌套 → JSON 对象嵌套;
Profile结构体 → JSON 里的profile对象;- 层级结构和结构体完全一致。
2. inline:扁平化展开(无嵌套)
去掉嵌套层级,把内部结构体字段直接展开到外层。
go
package main
import (
"encoding/json"
"fmt"
)
type Profile struct {
Address string `json:"address"`
}
type User struct {
Name string `json:"username"`
Profile `json:",inline"` // 扁平化
}
func main() {
u := User{Name: "张三", Profile: Profile{Address: "北京"}}
bytes, _ := json.Marshal(u)
fmt.Println(string(bytes))
}
✅ 运行结果:
{"username":"张三","address":"北京"}
✅ 结果解释:
inline作用:拆掉嵌套,字段平铺;- 没有
profile对象了,address直接和username同级; - 适合不需要层级、简化 JSON 的场景。
五、完整实战示例(序列化 + 反序列化)
集合所有 tag 用法,一站式掌握。
go
package main
import (
"encoding/json"
"fmt"
)
// 定义结构体 + 完整 Tag
type Employee struct {
Name string `json:"emp_name"`
Age int `json:"emp_age,string"`
Salary float64 `json:"salary,omitempty"`
Password string `json:"-"`
Hobbies []string `json:"hobbies"`
}
func main() {
// 1. 序列化:Go → JSON
emp := Employee{
Name: "李四",
Age: 25,
Hobbies: []string{"跑步", "编程"},
}
data, _ := json.MarshalIndent(emp, "", " ")
fmt.Println("序列化结果:")
fmt.Println(string(data))
// 2. 反序列化:JSON → Go
jsonStr := `{"emp_name":"李四","emp_age":"25","hobbies":["跑步","编程"]}`
var emp2 Employee
_ = json.Unmarshal([]byte(jsonStr), &emp2)
fmt.Println("\n反序列化结果:")
fmt.Printf("姓名:%s,年龄:%d\n", emp2.Name, emp2.Age)
}
✅ 运行结果:
序列化结果:
{
"emp_name": "李四",
"emp_age": "25",
"hobbies": [
"跑步",
"编程"
]
}
反序列化结果:
姓名:李四,年龄:25
✅ 结果解释:
- 序列化:自定义键名、数字转字符串、忽略空值、隐藏密码全部生效;
- 反序列化:JSON 能正确转回结构体,tag 规则双向生效;
- 这就是项目中最常用的完整写法。
六、Tag 必知规则(避坑指南)
- 结构体字段必须大写(导出字段),小写字段无论加什么 Tag 都无效;
- Tag 语法严格:无空格 ,逗号分隔参数(
json:"name,omitempty"); - 多个参数顺序无关:
json:"age,string,omitempty"等价于json:"age,omitempty,string"; - 反序列化时,JSON 键不区分大小写,但推荐严格匹配 Tag 名称。
七、常用 Tag 参数速查表
| Tag 写法 | 作用 |
|---|---|
json:"name" |
自定义 JSON 键名 |
json:"name,omitempty" |
自定义键名 + 空值忽略 |
json:"-" |
完全忽略字段 |
json:"name,string" |
强制转为 JSON 字符串 |
json:",inline" |
嵌套结构扁平化 |
总结
Go 的 JSON Tag 是处理 JSON 转换的核心利器:
- 用
json:"key"解决字段名映射; - 用
omitempty精简空值; - 用
-隐藏敏感字段; - 用
string适配类型差异。
掌握这些用法,就能轻松应对 Go 项目中 99% 的 JSON 序列化/反序列化场景!