目录

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 字段时,MaxRetrynil;当值为 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.NewDecoderDisallowUnknownFields() 方法。

Q3:JSON 中的 null 值会怎么处理?

如果对应字段是指针类型,解析后值为 nil;如果是非指针类型,字段保持零值。

Q4:能不能把 JSON 解析到 map 里再转成 Struct?

可以,但没必要。直接用 json.Unmarshal 解析到结构体是最高效的方式。两步转换不仅多余,还会带来性能损耗和类型安全问题。

Q5:大量 JSON 数据解析性能不够怎么办?

标准库的 encoding/json 在性能敏感的场景下可能不够快。可以考虑第三方库如 json-iteratorsonic,它们在保持 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/