JSON处理
第21课:JSON处理
生活类比
想象你是一位翻译官,JSON就是世界上使用最广泛的"通用语言"。当Go程序需要与其他系统(前端、API、数据库)交流时,你需要:
- 编码(Marshal):把Go的结构体"翻译"成JSON格式发送出去
- 解码(Unmarshal):把收到的JSON"翻译"回Go结构体使用
就像翻译官需要了解两种语言的规则和习惯用语一样,Go的encoding/json包就是你处理JSON的得力工具。
核心概念
| 概念 | 说明 |
|---|---|
| 序列化(Marshal) | 将Go数据结构转换为JSON字节切片 |
| 反序列化(Unmarshal) | 将JSON字节切片解析为Go数据结构 |
| 结构体标签(Struct Tag) | 控制JSON字段名和行为的元数据 |
| 流式处理 | 使用Decoder/Encoder处理大量数据或网络流 |
| 自定义序列化 | 实现Marshaler/Unmarshaler接口自定义转换逻辑 |
基本语法与用法
1. 导入包
GO
import "encoding/json"
2. 序列化:结构体 → JSON
GO
// 定义结构体
type User struct {
Name string
Age int
Email string
}
user := User{Name: "张三", Age: 28, Email: "zhangsan@example.com"}
// 序列化
data, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
// 输出: {"Name":"张三","Age":28,"Email":"zhangsan@example.com"}
💡 提示:
json.Marshal返回的是[]byte,需要用string()转换才能打印可读的JSON。
3. 反序列化:JSON → 结构体
GO
jsonStr := `{"Name":"李四","Age":32,"Email":"lisi@example.com"}`
var user User
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
log.Fatal(err)
}
fmt.Printf("姓名: %s, 年龄: %d\n", user.Name, user.Age)
// 输出: 姓名: 李四, 年龄: 32
💡 提示:
Unmarshal的第二个参数必须是指针,否则修改不会生效。
4. 结构体标签(Struct Tag)
GO
type Product struct {
ID int `json:"id"` // 指定JSON字段名
Name string `json:"name"` // 小写命名更符合JSON习惯
Price float64 `json:"price"`
Desc string `json:"description,omitempty"` // 空值时省略
internal string `json:"-"` // 完全忽略此字段
}
💡 提示:
omitempty:当字段为零值时,JSON输出中会省略该字段-:该字段永远不会出现在JSON中- 标签中的名字优先于字段名
5. 常用类型映射
| Go类型 | JSON类型 |
|---|---|
string |
string |
int, float64 |
number |
bool |
boolean |
nil |
null |
[]T |
array |
map[string]T |
object |
struct |
object |
示例
示例:基本的JSON序列化与反序列化(难度⭐)
GO
package main
import (
"encoding/json"
"fmt"
"log"
)
// Book 图书结构体
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Pages int `json:"pages"`
Tags []string `json:"tags"`
InStock bool `json:"in_stock"`
}
func main() {
// === 序列化 ===
book := Book{
Title: "Go语言实战",
Author: "李明",
Pages: 350,
Tags: []string{"编程", "Go", "后端"},
InStock: true,
}
// 美化输出(带缩进)
jsonData, err := json.MarshalIndent(book, "", " ")
if err != nil {
log.Fatal("序列化失败:", err)
}
fmt.Println("=== 序列化结果 ===")
fmt.Println(string(jsonData))
// === 反序列化 ===
jsonStr := `{
"title": "深入理解Go",
"author": "王强",
"pages": 480,
"tags": ["Go", "高级", "并发"],
"in_stock": false
}`
var newBook Book
err = json.Unmarshal([]byte(jsonStr), &newBook)
if err != nil {
log.Fatal("反序列化失败:", err)
}
fmt.Println("\n=== 反序列化结果 ===")
fmt.Printf("书名: %s\n", newBook.Title)
fmt.Printf("作者: %s\n", newBook.Author)
fmt.Printf("标签: %v\n", newBook.Tags)
fmt.Printf("在售: %v\n", newBook.InStock)
}
输出:
TEXT
=== 序列化结果 ===
{
"title": "Go语言实战",
"author": "李明",
"pages": 350,
"tags": [
"编程",
"Go",
"后端"
],
"in_stock": true
}
=== 反序列化结果 ===
书名: 深入理解Go
作者: 王强
标签: [Go 高级 并发]
在售: false
示例:嵌套JSON与map处理(难度⭐⭐)
GO
package main
import (
"encoding/json"
"fmt"
"log"
)
// Address 地址结构体
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code"`
}
// Contact 联系方式
type Contact struct {
Phone string `json:"phone"`
Email string `json:"email"`
}
// Employee 员工结构体(包含嵌套)
type Employee struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"` // 嵌套结构体
Contact Contact `json:"contact"` // 嵌套结构体
Skills []string `json:"skills"` // 切片
Metadata map[string]string `json:"metadata"` // 动态字段
}
func main() {
// 构造嵌套数据
emp := Employee{
Name: "赵六",
Age: 35,
Address: Address{
City: "北京",
Street: "朝阳区建国路88号",
ZipCode: "100022",
},
Contact: Contact{
Phone: "13800138000",
Email: "zhaoliu@example.com",
},
Skills: []string{"Go", "Python", "Docker"},
Metadata: map[string]string{
"department": "技术部",
"level": "P7",
"joined": "2020-03-15",
},
}
// 序列化
data, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("=== 嵌套JSON序列化 ===")
fmt.Println(string(data))
// 处理动态JSON(使用map)
dynamicJSON := `{
"event": "user_login",
"timestamp": 1700000000,
"data": {
"user_id": 12345,
"ip": "192.168.1.100",
"browser": "Chrome"
},
"tags": ["web", "auth"]
}`
var result map[string]interface{}
err = json.Unmarshal([]byte(dynamicJSON), &result)
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== 动态JSON解析 ===")
fmt.Printf("事件: %s\n", result["event"])
fmt.Printf("时间戳: %.0f\n", result["timestamp"])
// 访问嵌套的map
if data, ok := result["data"].(map[string]interface{}); ok {
fmt.Printf("用户ID: %.0f\n", data["user_id"])
fmt.Printf("IP地址: %s\n", data["ip"])
}
// 访问数组
if tags, ok := result["tags"].([]interface{}); ok {
fmt.Print("标签: ")
for _, tag := range tags {
fmt.Printf("%s ", tag)
}
fmt.Println()
}
}
输出:
TEXT
=== 嵌套JSON序列化 ===
{
"name": "赵六",
"age": 35,
"address": {
"city": "北京",
"street": "朝阳区建国路88号",
"zip_code": "100022"
},
"contact": {
"phone": "13800138000",
"email": "zhaoliu@example.com"
},
"skills": [
"Go",
"Python",
"Docker"
],
"metadata": {
"department": "技术部",
"joined": "2020-03-15",
"level": "P7"
}
}
=== 动态JSON解析 ===
事件: user_login
时间戳: 1700000000
用户ID: 12345
IP地址: 192.168.1.100
标签: web auth
示例:自定义序列化与流式处理(难度⭐⭐⭐)
GO
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
"time"
)
// CustomTime 自定义时间类型
type CustomTime struct {
time.Time
}
// 实现 json.Marshaler 接口
func (ct CustomTime) MarshalJSON() ([]byte, error) {
// 输出格式:2006-01-02 15:04:05
formatted := ct.Format("2006-01-02 15:04:05")
return json.Marshal(formatted)
}
// 实现 json.Unmarshaler 接口
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
// 支持多种格式解析
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006/01/02",
}
for _, format := range formats {
t, err := time.Parse(format, s)
if err == nil {
ct.Time = t
return nil
}
}
return fmt.Errorf("无法解析时间: %s", s)
}
// Status 自定义枚举类型
type Status int
const (
StatusActive Status = iota // 0
StatusInactive // 1
StatusBanned // 2
)
// 状态到字符串的映射
var statusNames = map[Status]string{
StatusActive: "active",
StatusInactive: "inactive",
StatusBanned: "banned",
}
// 字符串到状态的映射
var statusValues = map[string]Status{
"active": StatusActive,
"inactive": StatusInactive,
"banned": StatusBanned,
}
// MarshalJSON 自定义序列化
func (s Status) MarshalJSON() ([]byte, error) {
name, ok := statusNames[s]
if !ok {
return json.Marshal("unknown")
}
return json.Marshal(name)
}
// UnmarshalJSON 自定义反序列化
func (s *Status) UnmarshalJSON(data []byte) error {
var name string
if err := json.Unmarshal(data, &name); err != nil {
return err
}
val, ok := statusValues[name]
if !ok {
return fmt.Errorf("未知状态: %s", name)
}
*s = val
return nil
}
// EventLog 事件日志
type EventLog struct {
Event string `json:"event"`
Timestamp CustomTime `json:"timestamp"`
Status Status `json:"status"`
Details string `json:"details,omitempty"`
}
func main() {
// === 自定义序列化演示 ===
logEntry := EventLog{
Event: "user_register",
Timestamp: CustomTime{time.Date(2024, 1, 15, 14, 30, 0, 0, time.Local)},
Status: StatusActive,
Details: "新用户注册成功",
}
data, _ := json.MarshalIndent(logEntry, "", " ")
fmt.Println("=== 自定义序列化 ===")
fmt.Println(string(data))
// === 自定义反序列化演示 ===
jsonStr := `{
"event": "user_login",
"timestamp": "2024/01/15",
"status": "inactive"
}`
var entry EventLog
err := json.Unmarshal([]byte(jsonStr), &entry)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\n解析结果: 事件=%s, 时间=%s, 状态=%d\n",
entry.Event,
entry.Timestamp.Format("2006-01-02 15:04:05"),
entry.Status,
)
// === 流式处理演示 ===
fmt.Println("\n=== 流式Decoder ===")
// 模拟从网络接收的JSON流
jsonStream := `[
{"name": "Alice", "score": 95},
{"name": "Bob", "score": 87},
{"name": "Charlie", "score": 92}
]`
decoder := json.NewDecoder(strings.NewReader(jsonStream))
// 读取开始标记
token, err := decoder.Token()
if err != nil {
log.Fatal(err)
}
fmt.Printf("开始标记: %v\n", token)
// 逐个读取数组元素
type Student struct {
Name string `json:"name"`
Score int `json:"score"`
}
var students []Student
for decoder.More() {
var s Student
if err := decoder.Decode(&s); err != nil {
log.Fatal(err)
}
students = append(students, s)
}
for _, s := range students {
fmt.Printf("学生: %s, 分数: %d\n", s.Name, s.Score)
}
// === 流式Encoder演示 ===
fmt.Println("\n=== 流式Encoder ===")
var buf strings.Builder
encoder := json.NewEncoder(&buf)
encoder.SetIndent("", " ")
// 编码单个对象
for _, s := range students {
if err := encoder.Encode(s); err != nil {
log.Fatal(err)
}
}
fmt.Println(buf.String())
}
输出:
TEXT
=== 自定义序列化 ===
{
"event": "user_register",
"timestamp": "2024-01-15 14:30:00",
"status": "active",
"details": "新用户注册成功"
}
解析结果: 事件=user_login, 时间=2024-01-15 00:00:00, 状态=1
=== 流式Decoder ===
开始标记: [
学生: Alice, 分数: 95
学生: Bob, 分数: 87
学生: Charlie, 分数: 92
=== 流式Encoder ===
{
"name": "Alice",
"score": 95
}
{
"name": "Bob",
"score": 87
}
{
"name": "Charlie",
"score": 92
}
应用场景
场景1:API响应封装
GO
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
// APIResponse 统一API响应结构
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// SuccessResponse 成功响应
func SuccessResponse(w http.ResponseWriter, data interface{}) {
resp := APIResponse{
Code: 200,
Message: "success",
Data: data,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}
// ErrorResponse 错误响应
func ErrorResponse(w http.ResponseWriter, statusCode int, errMsg string) {
resp := APIResponse{
Code: statusCode,
Message: "error",
Error: errMsg,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(resp)
}
// UserHandler 处理用户请求
func UserHandler(w http.ResponseWriter, r *http.Request) {
// 模拟用户数据
users := []map[string]interface{}{
{"id": 1, "name": "张三", "role": "admin"},
{"id": 2, "name": "李四", "role": "user"},
{"id": 3, "name": "王五", "role": "user"},
}
SuccessResponse(w, users)
}
func main() {
// 模拟API响应
fmt.Println("=== 模拟API响应 ===")
// 成功响应
successResp := APIResponse{
Code: 200,
Message: "success",
Data: map[string]interface{}{
"id": 1,
"name": "张三",
},
}
data, _ := json.MarshalIndent(successResp, "", " ")
fmt.Println("成功响应:")
fmt.Println(string(data))
// 错误响应
errorResp := APIResponse{
Code: 404,
Message: "error",
Error: "用户不存在",
}
data, _ = json.MarshalIndent(errorResp, "", " ")
fmt.Println("\n错误响应:")
fmt.Println(string(data))
_ = log.Fatal // 避免未使用警告
}
输出:
TEXT
=== 模拟API响应 ===
成功响应:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "张三"
}
}
错误响应:
{
"code": 404,
"message": "error",
"error": "用户不存在"
}
场景2:配置文件读取与验证
GO
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
// DatabaseConfig 数据库配置
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
DBName string `json:"dbname"`
}
// ServerConfig 服务器配置
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
ReadTimeout int `json:"read_timeout"`
WriteTimeout int `json:"write_timeout"`
AllowOrigins []string `json:"allow_origins"`
}
// AppConfig 应用总配置
type AppConfig struct {
AppName string `json:"app_name"`
Debug bool `json:"debug"`
Server ServerConfig `json:"server"`
Database DatabaseConfig `json:"database"`
}
// Validate 验证配置
func (c *AppConfig) Validate() error {
if c.AppName == "" {
return fmt.Errorf("app_name不能为空")
}
if c.Server.Port <= 0 || c.Server.Port > 65535 {
return fmt.Errorf("server.port必须在1-65535之间")
}
if c.Database.Host == "" {
return fmt.Errorf("database.host不能为空")
}
return nil
}
func main() {
// 模拟配置文件内容
configJSON := `{
"app_name": "GoWebApp",
"debug": true,
"server": {
"host": "0.0.0.0",
"port": 8080,
"read_timeout": 30,
"write_timeout": 30,
"allow_origins": ["http://localhost:3000", "https://example.com"]
},
"database": {
"host": "localhost",
"port": 3306,
"username": "root",
"password": "secret123",
"dbname": "myapp"
}
}`
// 解析配置
var config AppConfig
err := json.Unmarshal([]byte(configJSON), &config)
if err != nil {
log.Fatalf("解析配置失败: %v", err)
}
// 验证配置
if err := config.Validate(); err != nil {
log.Fatalf("配置验证失败: %v", err)
}
// 打印配置信息
fmt.Printf("应用名称: %s\n", config.AppName)
fmt.Printf("调试模式: %v\n", config.Debug)
fmt.Printf("服务器地址: %s:%d\n", config.Server.Host, config.Server.Port)
fmt.Printf("数据库连接: %s:%d/%s\n",
config.Database.Host,
config.Database.Port,
config.Database.DBName,
)
fmt.Printf("允许的来源: %v\n", config.Server.AllowOrigins)
// 写入示例(保存修改后的配置)
config.Debug = false
config.Server.Port = 9090
output, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== 修改后的配置 ===")
fmt.Println(string(output))
// 实际项目中会写入文件:
// os.WriteFile("config.json", output, 0644)
_ = os.WriteFile // 避免未使用警告
}
输出:
TEXT
应用名称: GoWebApp
调试模式: true
服务器地址: 0.0.0.0:8080
数据库连接: localhost:3306/myapp
允许的来源: [http://localhost:3000 https://example.com]
=== 修改后的配置 ===
{
"app_name": "GoWebApp",
"debug": false,
"server": {
"host": "0.0.0.0",
"port": 9090,
"read_timeout": 30,
"write_timeout": 30,
"allow_origins": [
"http://localhost:3000",
"https://example.com"
]
},
"database": {
"host": "localhost",
"port": 3306,
"username": "root",
"password": "secret123",
"dbname": "myapp"
}
}
❓ 常见问题
Q1:为什么JSON字段名是大写的?
原因:Go只导出首字母大写的字段,而json.Marshal默认使用字段名作为JSON键。
解决方案:使用结构体标签指定小写名称:
GO
type User struct {
Name string `json:"name"` // JSON中为 "name"
Age int `json:"age"` // JSON中为 "age"
Email string `json:"email"` // JSON中为 "email"
}
Q2:如何忽略空值字段?
使用omitempty标签:
GO
type Request struct {
Name string `json:"name"`
Email string `json:"email,omitempty"` // 空字符串时省略
Age int `json:"age,omitempty"` // 0时省略
Items []string `json:"items,omitempty"` // nil或空切片时省略
}
// 测试
req := Request{Name: "张三"}
data, _ := json.Marshal(req)
fmt.Println(string(data))
// 输出: {"name":"张三"} — email、age、items都被省略
Q3:如何处理JSON中的数字精度问题?
Go的json.Unmarshal默认将JSON数字解析为float64,大整数会丢失精度:
GO
// 问题示例
var result map[string]interface{}
json.Unmarshal([]byte(`{"id": 12345678901234567}`), &result)
fmt.Printf("%.0f\n", result["id"]) // 输出: 12345678901234568(精度丢失!)
// 解决方案:使用json.Number
decoder := json.NewDecoder(strings.NewReader(`{"id": 12345678901234567}`))
decoder.UseNumber()
decoder.Decode(&result)
id, _ := result["id"].(json.Number).Int64()
fmt.Println(id) // 输出: 12345678901234567(正确)
Q4:如何处理未知结构的JSON?
使用map[string]interface{}或json.RawMessage:
GO
// 方式1:使用map
var data map[string]interface{}
json.Unmarshal(jsonBytes, &data)
// 方式2:使用json.RawMessage延迟解析
type Message struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"` // 延迟解析
}
// 根据Type字段决定如何解析Payload
switch msg.Type {
case "user":
var user User
json.Unmarshal(msg.Payload, &user)
case "order":
var order Order
json.Unmarshal(msg.Payload, &order)
}
📖 小节
本节课我们学习了Go语言JSON处理的核心内容:
- 基本操作:
json.Marshal序列化和json.Unmarshal反序列化 - 结构体标签:使用
json:"name"控制字段名,omitempty省略空值,-忽略字段 - 嵌套处理:结构体嵌套、
map[string]interface{}处理动态JSON - 自定义序列化:实现
Marshaler/Unmarshaler接口 - 流式处理:
json.Decoder和json.Encoder处理流数据 - 实际应用:API响应封装、配置文件管理
💡 核心要点:
- 始终检查错误处理
- 反序列化时传入指针
- 使用结构体标签保持JSON命名规范
- 大数据量使用流式处理
📝 作业
练习1:基础练习
编写程序,定义一个Student结构体(包含姓名、年龄、成绩列表),实现:
- 创建3个学生对象
- 序列化为JSON数组并美化输出
- 反序列化回结构体并打印信息
练习2:进阶练习
实现一个简单的JSON配置管理器:
- 定义应用配置结构体(包含服务器、数据库、日志等配置)
- 实现
LoadConfig(filename)函数从文件读取配置 - 实现
SaveConfig(filename, config)函数保存配置到文件 - 实现配置验证功能
练习3:高级练习
实现一个JSON-RPC消息处理器:
- 定义请求和响应结构
- 使用
json.RawMessage实现延迟解析 - 根据请求方法名分发到不同的处理函数
- 支持批量请求处理
GO
// 提示:JSON-RPC请求格式
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
ID interface{} `json:"id"`
}
type RPCResponse struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
ID interface{} `json:"id"`
}
下一课
完成本课学习后,请继续学习 第22课:HTTP服务,我们将学习如何使用Go构建HTTP服务器和客户端。



