Go 语言 JSON 字符串转 Struct 完整教程 | 嵌套解析与实战技巧
title = “Go 语言 JSON 字符串转 Struct 完整教程 | 嵌套解析与实战技巧” description = “详解 Go 语言中 JSON 字符串转换为 Struct 的多种方法,涵盖基础解析、嵌套 JSON 处理、动态字段映射及常见踩坑指南,附完整代码示例,助你快速掌握 Golang JSON 解析核心技能。” keywords = “Go JSON 转 Struct, Golang JSON 解析, Go encoding/json, JSON 反序列化, Go 结构体映射” categories = [“编程开发”] tags = [“Go”, “JSON”, “Struct”, “encoding/json”, “Golang 数据解析”, “后端开发”] slug = “go-json-string-to-struct” date = “2026-05-03” lastmod = “2026-05-03” summary = "" draft = false type = “posts” weight = 0 include_toc = false show_comments = true
Go 语言 JSON 字符串转 Struct 完整教程:从入门到实战
在日常后端开发中,和 JSON 数据打交道几乎是家常便饭。不管是对接第三方 API、读取配置文件,还是处理前端请求体,我们都需要把 JSON 字符串转换成 Go 的结构体(Struct)来使用。Go 标准库 encoding/json 提供了简洁而强大的反序列化能力,但实际使用中坑也不少。
这篇文章会从最基础的用法讲起,逐步深入到嵌套结构体、动态字段、自定义解析等场景,帮你彻底搞定 Go 中 JSON 转 Struct 的各种需求。
基础用法:json.Unmarshal 快速上手
Go 标准库里的 json.Unmarshal 是最常用的 JSON 解析函数。它的工作方式很直接——你给它一段 JSON 字节切片和一个目标结构体指针,它就帮你把数据填进去。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
func main() {
jsonStr := `{"name": "张三", "age": 28, "email": "zhangsan@example.com"}`
var user User
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
fmt.Println("解析失败:", err)
return
}
fmt.Printf("姓名: %s, 年龄: %d, 邮箱: %s\n", user.Name, user.Age, user.Email)
}运行结果:
姓名: 张三, 年龄: 28, 邮箱: zhangsan@example.com这里有几个关键点需要注意:
json.Unmarshal的第一个参数是[]byte类型,所以字符串需要先转换- 第二个参数必须传指针,否则无法修改目标变量的值
- 结构体字段必须是导出的(首字母大写),
encoding/json才能访问到
结构体标签 json tag 详解
结构体标签(struct tag)是 Go 解析 JSON 时的核心机制。通过 json:"xxx" 这样的标签,你可以灵活控制字段和 JSON key 之间的映射关系。
基本映射
type Product struct {
ProductName string `json:"product_name"`
Price float64 `json:"price"`
InStock bool `json:"in_stock"`
}这样即使 JSON 中的 key 是下划线风格,Go 结构体字段可以保持驼峰命名,互不影响。
忽略字段
如果某个字段你不想参与 JSON 的序列化和反序列化,用 json:"-" 即可:
type Account struct {
Username string `json:"username"`
Password string `json:"-"` // 这个字段不会出现在 JSON 中
}omitempty:忽略零值
加上 omitempty 选项后,当字段值为零值(空字符串、0、false、nil 等)时,序列化时会自动跳过该字段:
type Profile struct {
Nickname string `json:"nickname,omitempty"`
Bio string `json:"bio,omitempty"`
}需要注意的是,omitempty 只影响序列化(Struct 转 JSON),不影响反序列化(JSON 转 Struct)。
嵌套 JSON 结构体解析
真实业务中的 JSON 数据往往不是扁平的,而是带有嵌套层级的。处理方式其实也很自然——在结构体内部嵌套其他结构体就行。
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
Province string `json:"province"`
City string `json:"city"`
Street string `json:"street"`
}
type Employee struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"`
}
func main() {
jsonStr := `{
"name": "李四",
"age": 32,
"address": {
"province": "广东省",
"city": "深圳市",
"street": "南山区科技园路 88 号"
}
}`
var emp Employee
if err := json.Unmarshal([]byte(jsonStr), &emp); err != nil {
fmt.Println("解析失败:", err)
return
}
fmt.Printf("%s 住在 %s%s%s\n", emp.Name, emp.Address.Province, emp.Address.City, emp.Address.Street)
}运行结果:
李四 住在 广东省深圳市南山区科技园路 88 号对于更深层级的嵌套,思路完全一致,只要结构体定义和 JSON 的层级结构对应上就可以了。
处理动态或不确定的 JSON 字段
有时候你不确定 JSON 里会有哪些字段,或者字段的类型不固定。这种情况下用 map[string]interface{} 来接收是最灵活的方案。
package main
import (
"encoding/json"
"fmt"
)
func main() {
jsonStr := `{"name": "王五", "score": 95.5, "passed": true, "extra": {"note": "优秀"}}`
var result map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
fmt.Println("解析失败:", err)
return
}
for key, value := range result {
fmt.Printf("键: %s, 值: %v, 类型: %T\n", key, value, value)
}
}运行结果:
键: name, 值: 王五, 类型: string
键: score, 值: 95.5, 类型: float64
键: passed, 值: true, 类型: bool
键: extra, 值: map[note:优秀], 类型: map[string]interface {}使用 map[string]interface{} 时要注意一点:JSON 里的数字类型统统会被解析成 float64,哪怕原始值看起来是整数。如果你需要精确的整数类型,可以用 json.Number 来处理:
package main
import (
"encoding/json"
"fmt"
"strings"
)
func main() {
jsonStr := `{"id": 10086, "amount": 99.9}`
decoder := json.NewDecoder(strings.NewReader(jsonStr))
decoder.UseNumber()
var result map[string]interface{}
if err := decoder.Decode(&result); err != nil {
fmt.Println("解析失败:", err)
return
}
id := result["id"].(json.Number)
idInt, _ := id.Int64()
fmt.Printf("ID: %d, 类型: %T\n", idInt, idInt)
}JSON 数组转 Struct 切片
当 JSON 数据是一个数组时,直接用切片来接收:
package main
import (
"encoding/json"
"fmt"
)
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
}
func main() {
jsonStr := `[
{"id": 1, "title": "完成需求文档", "status": "done"},
{"id": 2, "title": "编写单元测试", "status": "in_progress"},
{"id": 3, "title": "部署上线", "status": "pending"}
]`
var tasks []Task
if err := json.Unmarshal([]byte(jsonStr), &tasks); err != nil {
fmt.Println("解析失败:", err)
return
}
for _, t := range tasks {
fmt.Printf("任务 #%d: %s [%s]\n", t.ID, t.Title, t.Status)
}
}运行结果:
任务 #1: 完成需求文档 [done]
任务 #2: 编写单元测试 [in_progress]
任务 #3: 部署上线 [pending]自定义 JSON 反序列化逻辑
标准的反序列化行为有时候满足不了需求。比如 JSON 里的时间字符串格式不是 RFC3339,或者某个字段需要特殊的转换逻辑。这时候可以通过实现 json.Unmarshaler 接口来自定义解析行为。
package main
import (
"encoding/json"
"fmt"
"time"
)
type CustomTime struct {
time.Time
}
func (ct *CustomTime) UnmarshalJSON(b []byte) error {
// 去掉 JSON 字符串两端的引号
s := string(b)
s = s[1 : len(s)-1]
t, err := time.Parse("2006-01-02 15:04:05", s)
if err != nil {
return err
}
ct.Time = t
return nil
}
type Event struct {
Title string `json:"title"`
StartTime CustomTime `json:"start_time"`
}
func main() {
jsonStr := `{"title": "项目评审会议", "start_time": "2026-03-15 14:30:00"}`
var event Event
if err := json.Unmarshal([]byte(jsonStr), &event); err != nil {
fmt.Println("解析失败:", err)
return
}
fmt.Printf("事件: %s, 开始时间: %s\n", event.Title, event.StartTime.Format("2006 年 01 月 02 日 15:04"))
}运行结果:
事件: 项目评审会议, 开始时间: 2026 年 03 月 15 日 14:30通过自定义 UnmarshalJSON 方法,你可以对任意字段实现完全可控的解析逻辑。这在处理不规范的第三方 API 返回值时特别有用。
实际项目中的踩坑经验
在真实项目里用 JSON 转 Struct,有些坑踩过一次就会印象深刻。这里总结几个高频问题。
1. 字段首字母没有大写
这是新手最容易犯的错误。Go 语言中只有首字母大写的字段才是导出字段,encoding/json 只能访问导出字段:
// 错误写法:name 是未导出字段,JSON 解析时会被忽略
type User struct {
name string `json:"name"`
}
// 正确写法
type User struct {
Name string `json:"name"`
}2. JSON 数字精度丢失
当 JSON 中包含很大的整数(比如雪花算法生成的 ID),用 interface{} 接收时会被转成 float64,导致精度丢失。前面提到的 json.NewDecoder 配合 UseNumber() 可以解决这个问题。
3. 空值和零值的区别
Go 的结构体字段有零值的概念。如果 JSON 中某个字段缺失,对应的结构体字段会保持零值。但零值和"显式传了一个零"是两种不同的语义。如果你需要区分这两种情况,可以用指针类型:
type Config struct {
MaxRetry *int `json:"max_retry"` // 用指针区分 "没传" 和 "传了 0"
}当 JSON 中没有 max_retry 字段时,MaxRetry 为 nil;当值为 0 时,MaxRetry 指向一个值为 0 的 int。
4. json.Decoder 和 json.Unmarshal 怎么选
简单场景直接用 json.Unmarshal 就够了。但如果数据来源是 io.Reader(比如 HTTP 请求的 Body),用 json.NewDecoder 更合适,因为它支持流式读取,不需要先把全部内容读到内存中:
// 处理 HTTP 请求体的推荐写法
func handleRequest(w http.ResponseWriter, r *http.Request) {
var req MyRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "请求体格式错误", http.StatusBadRequest)
return
}
// 业务逻辑处理...
}常见问题
Q1:JSON 字段名和 Go 结构体字段名不一致怎么办?
使用 json:"xxx" 结构体标签来指定映射关系。比如 JSON 中是 user_name,Go 中可以写成 UserName string \json:“user_name”``。
Q2:如何忽略 JSON 中的未知字段?
默认情况下 json.Unmarshal 就会忽略结构体中不存在的 JSON 字段,不需要额外配置。如果你反而想在遇到未知字段时报错,可以使用 json.NewDecoder 的 DisallowUnknownFields() 方法。
Q3:JSON 中的 null 值会怎么处理?
如果对应字段是指针类型,解析后值为 nil;如果是非指针类型,字段保持零值。
Q4:能不能把 JSON 解析到 map 里再转成 Struct?
可以,但没必要。直接用 json.Unmarshal 解析到结构体是最高效的方式。两步转换不仅多余,还会带来性能损耗和类型安全问题。
Q5:大量 JSON 数据解析性能不够怎么办?
标准库的 encoding/json 在性能敏感的场景下可能不够快。可以考虑第三方库如 json-iterator 或 sonic,它们在保持 API 兼容的同时提供了显著的性能提升。
Q6:嵌套层级很深的 JSON 怎么处理?
对于层级很深但你只关心部分字段的情况,可以用 json.RawMessage 延迟解析:
type Response struct {
Code int `json:"code"`
Data json.RawMessage `json:"data"` // 先不解析,后续按需处理
}这样可以根据 Code 的值决定 Data 要解析成什么类型,既灵活又高效。
总结
Go 语言中 JSON 字符串转 Struct 的核心就是 encoding/json 包。掌握了以下几个要点,日常开发基本可以游刃有余:
- 基础解析:
json.Unmarshal搭配结构体标签,覆盖绝大多数场景 - 嵌套处理:结构体内嵌套结构体,和 JSON 层级一一对应即可
- 灵活接收:
map[string]interface{}应对动态字段,json.RawMessage实现延迟解析 - 自定义逻辑:实现
json.Unmarshaler接口,完全掌控解析过程 - 避坑指南:注意字段导出、数字精度、零值语义等细节问题
把这些用法吃透,不管遇到什么样的 JSON 数据,你都能用 Go 优雅地解析和处理。
如果大家对 Go 语言 JSON 解析还有哪些疑问,或者在实际项目中遇到了什么棘手的 JSON 处理问题,欢迎在评论区一起交流讨论~~~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go-json-string-to-struct/
备用原文链接: https://blog.fiveyoboy.com/articles/go-json-string-to-struct/