404 Not Found

404 Not Found


nginx

JSON处理

第21课:JSON处理

生活类比

想象你是一位翻译官,JSON就是世界上使用最广泛的"通用语言"。当Go程序需要与其他系统(前端、API、数据库)交流时,你需要:

就像翻译官需要了解两种语言的规则和习惯用语一样,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处理的核心内容:

  1. 基本操作json.Marshal序列化和json.Unmarshal反序列化
  2. 结构体标签:使用json:"name"控制字段名,omitempty省略空值,-忽略字段
  3. 嵌套处理:结构体嵌套、map[string]interface{}处理动态JSON
  4. 自定义序列化:实现Marshaler/Unmarshaler接口
  5. 流式处理json.Decoderjson.Encoder处理流数据
  6. 实际应用:API响应封装、配置文件管理
💡 核心要点

  • 始终检查错误处理
  • 反序列化时传入指针
  • 使用结构体标签保持JSON命名规范
  • 大数据量使用流式处理

📝 作业

练习1:基础练习

编写程序,定义一个Student结构体(包含姓名、年龄、成绩列表),实现:

  1. 创建3个学生对象
  2. 序列化为JSON数组并美化输出
  3. 反序列化回结构体并打印信息

练习2:进阶练习

实现一个简单的JSON配置管理器:

  1. 定义应用配置结构体(包含服务器、数据库、日志等配置)
  2. 实现LoadConfig(filename)函数从文件读取配置
  3. 实现SaveConfig(filename, config)函数保存配置到文件
  4. 实现配置验证功能

练习3:高级练习

实现一个JSON-RPC消息处理器:

  1. 定义请求和响应结构
  2. 使用json.RawMessage实现延迟解析
  3. 根据请求方法名分发到不同的处理函数
  4. 支持批量请求处理
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服务器和客户端。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏