JSON:工作里最常见的数据格式
本文是《Go语言大白话入门》系列教程的第 12 篇。本文只做一件事:把 JSON 和 Go 里的结构体连起来,先能跑通,再去记名字。
本章目标
这一章看完,你至少要知道四件事:
- JSON 是什么,平时拿来干什么;
- 为什么 Go 里经常拿结构体配 JSON;
- 结构体怎么转成 JSON,JSON 怎么再转回来;
- 字段首字母大小写、json 标签这些坑到底怎么避。
常见坑位
先把坑摆在前面,后面看代码会省很多事。
- 把 JSON 当成 Go 专属语法,其实它是程序之间通用的数据格式;
- 看到像字典的文本就以为能直接用,忘了 JSON 必须合法;
- 字段首字母写成小写,还指望 JSON 库照样能读到;
- 搞不清 Marshal 和 Unmarshal 的方向;
- 只盯着代码,不看错误信息。
JSON 是什么
可以把 JSON 理解成一种专门装数据的文本格式。
例如,一个用户信息可以写成这样:
json
{
"name": "小李",
"age": 20,
"city": "上海"
}
它看起来像一份排得很整齐的数据清单。说得再直白一点,JSON 不是 Go 的专用语法,而是很多程序都认的通用格式。
JSON 有什么用
JSON 最常见的作用,就是让不同程序之间方便交换数据。
常见场景有这些:
- 前端把注册信息传给后端;
- 后端把用户资料返回给前端;
- 程序把配置内容存进文件;
- 调第三方接口时接收返回结果。
你可以先把它理解成程序之间递来递去的一张数据纸条。能看懂这一点,后面学接口就会轻很多。
为什么 Go 常配合结构体使用 JSON
Go 里的结构体很适合表示一组有名字的数据,而 JSON 本身也是一组有名字的数据。
所以最常见的做法就是两种:
- 用结构体接住 JSON;
- 把结构体转成 JSON 发出去。
这样做有两个好处:
- 不用自己手拼一大串字符串;
- 代码更清楚,也更不容易出错。

先把这个来回记住就行:
- 结构体可以变成 JSON;
- JSON 也可以再变回结构体;
- 后面写接口、读接口,基本都会碰到这套动作。
结构体转 JSON
先跑一个最小例子,看看结构体怎么变成 JSON。
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
user := User{Name: "小李", Age: 20, City: "上海"}
data, err := json.Marshal(user)
if err != nil {
fmt.Println("转JSON失败:", err)
return
}
fmt.Println(string(data))
prettyData, err := json.MarshalIndent(user, "", " ")
if err != nil {
fmt.Println("格式化JSON失败:", err)
return
}
fmt.Println(string(prettyData))
}
这里有两个点先记住:
encoding/json是 Go 标准库里专门处理 JSON 的包;json.Marshal的意思,就是把 Go 里的数据转换成 JSON。
结构体字段后面这段 json:"name" 叫结构体标签。它是在告诉 JSON 库:这个字段转出去以后,名字叫 name,不是 Name。
运行后大概会看到这样的结果:
text
{"name":"小李","age":20,"city":"上海"}
{
"name": "小李",
"age": 20,
"city": "上海"
}
JSON 解析到结构体
再看反方向:把 JSON 字符串读进结构体。
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
jsonText := `{"name":"小李","age":20,"city":"上海"}`
var user User
err := json.Unmarshal([]byte(jsonText), &user)
if err != nil {
fmt.Println("解析失败:", err)
return
}
fmt.Println(user.Name)
fmt.Println(user.Age)
fmt.Println(user.City)
}
这里最容易漏的是 &user。你可以先把它当成一个固定写法:只要函数要往你的变量里写数据,通常就要传指针,也就是带上 &。
如果这里忘了加,常见报错就是 non-pointer。第一反应别想太多,先回头检查是不是把这个"口子"漏了。
json.Unmarshal 的作用可以先记成一句话:把 JSON 里的数据拆出来,再装回 Go 的结构体。
json 标签是做什么的
最常见的标签写法是这种:
go
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Age int `json:"age,omitempty"`
}
这里分两层看:
name是让 JSON 里的字段名和 Go 字段名分开;omitempty是说字段是零值时就别输出。
比如:
go
u := User{Name: "小李"}
转出来的 JSON 就会更干净,只保留有值的字段。
text
{"name":"小李"}
这个东西在接口返回里特别常见。没有的字段就别硬塞出来,返回会更清爽。
字段首字母大小写这个坑
这是新手最容易踩的地方。
go
type User struct {
name string `json:"name"`
}
这段写法通常不会得到你想要的结果。原因也不复杂:字段首字母小写时,Go 里这个字段对外部包不可见,JSON 库自然就拿不到它。
所以先记一条硬规则:只要这个字段要参与 JSON 处理,就写成大写开头。
换成大白话就是,字段不"露出来",外部库就看不见。JSON 库看不见,就没法帮你转。
嵌套结构体也能一起转
JSON 不只会处理单层数据。结构体里套结构体,它也能继续往下处理。
go
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
}
type User struct {
Name string `json:"name"`
Address Address `json:"address"`
}
func main() {
user := User{
Name: "小李",
Address: Address{
City: "上海",
},
}
data, err := json.Marshal(user)
if err != nil {
fmt.Println("转JSON失败:", err)
return
}
fmt.Println(string(data))
}
输出会像这样:
text
{"name":"小李","address":{"city":"上海"}}
这就是为什么 JSON 很适合做接口数据:层级清楚,程序也能顺着层级往下读。
排查顺序
如果你这章跑不通,先按这个顺序查:
- 结构体字段首字母是不是大写;
- json 标签有没有写错;
- JSON 字符串里的引号、逗号、大括号是否完整;
- 你是不是把 Marshal 和 Unmarshal 的方向看反了;
- 函数要写入变量时,有没有漏掉
&。
多数问题都能在这几步里找到,不用一上来就怀疑 Go 或怀疑自己。
练习任务
这章学完,建议自己动手做四件事:
- 定义一个
Product结构体,转成 JSON 打印出来; - 写一段 JSON 字符串,用
Unmarshal解析回结构体; - 故意把字段首字母改成小写,看看结果;
- 给某个字段加上
omitempty,看看零值时会不会被省掉。
本节最低通过线
这一章能过线,标准其实很简单:你能把 JSON 示例跑通,能说清字段是怎么对上的,也知道出问题先查哪一步。
前面这些内容学会以后,后面写 HTTP 接口、读配置文件、处理第三方返回值,都会顺很多。
本文章属于《Go语言大白话入门》零基础全套教程,持续更新、全程实操练手。 微信公众号【码栈研说】超前更新,目前比掘金领先 30 章内容,完整连载、无删减、持续日更,需要完整系统学习的读者可自行前往查看。
文中部分内容借助 AI 辅助梳理,全文人工校验、实操落地。