Skip to content

JSON 处理

1. 概述

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛应用于 Web 应用、API 通信和配置文件中。Go 语言的标准库 encoding/json 提供了强大的 JSON 处理功能,支持 JSON 数据的编码(序列化)和解码(反序列化)。

本章节将详细介绍 Go 语言中 JSON 处理的基本概念、使用方法、常见错误、应用场景和最佳实践,帮助开发者掌握 JSON 处理的技巧,实现高效的数据交换。

2. 基本概念

2.1 JSON 数据结构

JSON 支持以下数据结构:

  • 对象:键值对的集合,使用花括号 {} 表示
  • 数组:值的有序列表,使用方括号 [] 表示
  • 字符串:使用双引号 " 包围的字符序列
  • 数字:整数或浮点数
  • 布尔值truefalse
  • null:表示空值

2.2 Go 语言中的 JSON 处理

Go 语言的 encoding/json 包提供了以下核心功能:

  • Marshal:将 Go 数据结构编码为 JSON
  • Unmarshal:将 JSON 解码为 Go 数据结构
  • Encoder:流式编码 JSON
  • Decoder:流式解码 JSON

2.3 标签(Tags)

Go 语言使用结构体标签(tags)来控制 JSON 编码和解码的行为,常用的标签包括:

  • json:"name":指定 JSON 字段名
  • json:"-":忽略该字段
  • json:",omitempty":当字段为零值时忽略
  • json:",string":将字段编码为字符串

3. 原理深度解析

3.1 JSON 编码原理

JSON 编码(Marshal)的原理如下:

  1. 反射:使用 Go 的反射机制分析数据结构
  2. 类型转换:将 Go 类型转换为 JSON 类型
  3. 递归处理:递归处理嵌套的数据结构
  4. 标签处理:根据结构体标签控制编码行为
  5. 生成 JSON:生成符合 JSON 规范的字符串

3.2 JSON 解码原理

JSON 解码(Unmarshal)的原理如下:

  1. 解析 JSON:解析 JSON 字符串为内部数据结构
  2. 反射:使用 Go 的反射机制分析目标数据结构
  3. 类型匹配:将 JSON 类型匹配到 Go 类型
  4. 递归处理:递归处理嵌套的数据结构
  5. 填充数据:将解析后的数据填充到目标结构

3.3 流式处理原理

流式处理的原理如下:

  1. 缓冲机制:使用缓冲区提高 I/O 效率
  2. 增量处理:逐步处理数据,减少内存使用
  3. 错误处理:及时捕获和处理错误
  4. 类型推断:根据上下文推断数据类型

3.4 性能优化原理

JSON 处理的性能优化原理包括:

  1. 内存分配:减少内存分配和拷贝
  2. 类型选择:选择合适的 Go 类型
  3. 标签使用:合理使用结构体标签
  4. 流式处理:对于大文件使用流式处理
  5. 缓存:缓存常用的 JSON 数据

4. 常见错误与踩坑点

4.1 类型不匹配

错误表现

  • 解码失败,返回类型错误
  • 编码时字段被忽略
  • 运行时 panic

产生原因

  • JSON 数据类型与 Go 结构体字段类型不匹配
  • 结构体字段为非导出字段(首字母小写)
  • 指针类型处理不当

解决方案

  • 确保 JSON 数据类型与 Go 类型匹配
  • 确保结构体字段首字母大写(导出)
  • 正确使用指针类型

4.2 标签使用错误

错误表现

  • 字段名不符合预期
  • 零值字段未被忽略
  • 编码格式错误

产生原因

  • 标签语法错误
  • 标签名称错误
  • 标签选项使用错误

解决方案

  • 正确使用标签语法:json:"name,option"
  • 确保标签名称与 JSON 字段名一致
  • 正确使用标签选项

4.3 大文件处理

错误表现

  • 内存使用过高
  • 处理速度慢
  • 程序崩溃

产生原因

  • 直接使用 Marshal/Unmarshal 处理大文件
  • 未使用流式处理
  • 数据结构设计不合理

解决方案

  • 使用 Encoder/Decoder 进行流式处理
  • 分块处理大文件
  • 优化数据结构设计

4.4 时间处理

错误表现

  • 时间格式不符合预期
  • 时间解析失败
  • 时区处理错误

产生原因

  • 时间类型未正确标记
  • 时间格式不符合 JSON 标准
  • 时区设置错误

解决方案

  • 使用 time.Time 类型并正确设置标签
  • 使用标准的 ISO8601 时间格式
  • 正确处理时区

4.5 特殊字符处理

错误表现

  • JSON 解析失败
  • 特殊字符被转义
  • 编码后数据不符合预期

产生原因

  • 字符串中包含特殊字符
  • 未正确处理 Unicode 字符
  • 转义字符使用错误

解决方案

  • 确保字符串符合 JSON 规范
  • 正确处理 Unicode 字符
  • 使用标准的 JSON 转义规则

5. 常见应用场景

5.1 基本 JSON 编码

场景描述:将 Go 数据结构编码为 JSON 字符串

使用方法

  1. 定义结构体
  2. 使用 json.Marshal 编码
  3. 处理编码结果

示例代码

go
import (
    "encoding/json"
    "fmt"
)

// 定义结构体
type Person struct {
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Email   string `json:"email,omitempty"`
    Address string `json:"-"` // 忽略该字段
}

func main() {
    // 创建结构体实例
    person := Person{
        Name:    "John",
        Age:     30,
        Email:   "john@example.com",
        Address: "123 Main St",
    }
    
    // 编码为 JSON
    jsonData, err := json.Marshal(person)
    if err != nil {
        fmt.Println("编码失败:", err)
        return
    }
    
    fmt.Println("JSON 数据:", string(jsonData))
}

运行结果

JSON 数据: {"name":"John","age":30,"email":"john@example.com"}

5.2 基本 JSON 解码

场景描述:将 JSON 字符串解码为 Go 数据结构

使用方法

  1. 定义结构体
  2. 使用 json.Unmarshal 解码
  3. 处理解码结果

示例代码

go
import (
    "encoding/json"
    "fmt"
)

// 定义结构体
type Person struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email"`
}

func main() {
    // JSON 字符串
    jsonData := `{"name":"John","age":30,"email":"john@example.com"}`
    
    // 解码为结构体
    var person Person
    if err := json.Unmarshal([]byte(jsonData), &person); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    fmt.Println("姓名:", person.Name)
    fmt.Println("年龄:", person.Age)
    fmt.Println("邮箱:", person.Email)
}

运行结果

姓名: John
年龄: 30
邮箱: john@example.com

5.3 流式编码

场景描述:将 Go 数据结构流式编码到输出流

使用方法

  1. 创建 json.Encoder
  2. 使用 Encode 方法编码
  3. 处理编码结果

示例代码

go
import (
    "encoding/json"
    "fmt"
    "os"
)

// 定义结构体
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    // 创建编码器
    encoder := json.NewEncoder(os.Stdout)
    encoder.SetIndent("", "  ") // 设置缩进
    
    // 编码数据
    people := []Person{
        {Name: "John", Age: 30},
        {Name: "Alice", Age: 25},
        {Name: "Bob", Age: 35},
    }
    
    if err := encoder.Encode(people); err != nil {
        fmt.Println("编码失败:", err)
        return
    }
}

运行结果

json
[
  {
    "name": "John",
    "age": 30
  },
  {
    "name": "Alice",
    "age": 25
  },
  {
    "name": "Bob",
    "age": 35
  }
]

5.4 流式解码

场景描述:从输入流流式解码 JSON 数据

使用方法

  1. 创建 json.Decoder
  2. 使用 Decode 方法解码
  3. 处理解码结果

示例代码

go
import (
    "encoding/json"
    "fmt"
    "strings"
)

// 定义结构体
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    // JSON 字符串
    jsonData := `[
        {"name":"John","age":30},
        {"name":"Alice","age":25},
        {"name":"Bob","age":35}
    ]`
    
    // 创建解码器
    decoder := json.NewDecoder(strings.NewReader(jsonData))
    
    // 解码数组
    var people []Person
    if err := decoder.Decode(&people); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    // 输出结果
    for _, person := range people {
        fmt.Printf("姓名: %s, 年龄: %d\n", person.Name, person.Age)
    }
}

运行结果

姓名: John, 年龄: 30
姓名: Alice, 年龄: 25
姓名: Bob, 年龄: 35

5.5 动态 JSON 处理

场景描述:处理结构未知的 JSON 数据

使用方法

  1. 使用 interface{}map[string]interface{}
  2. 解码 JSON 数据
  3. 类型断言处理数据

示例代码

go
import (
    "encoding/json"
    "fmt"
)

func main() {
    // JSON 字符串
    jsonData := `{
        "name": "John",
        "age": 30,
        "isAdmin": true,
        "roles": ["user", "admin"],
        "address": {
            "city": "New York",
            "zip": 10001
        }
    }`
    
    // 解码为 map
    var data map[string]interface{}
    if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    // 处理数据
    fmt.Println("姓名:", data["name"])
    fmt.Println("年龄:", data["age"])
    fmt.Println("是否管理员:", data["isAdmin"])
    
    // 处理数组
    if roles, ok := data["roles"].([]interface{}); ok {
        fmt.Println("角色:")
        for _, role := range roles {
            fmt.Println("  -", role)
        }
    }
    
    // 处理嵌套对象
    if address, ok := data["address"].(map[string]interface{}); ok {
        fmt.Println("地址:")
        fmt.Println("  城市:", address["city"])
        fmt.Println("  邮编:", address["zip"])
    }
}

运行结果

姓名: John
年龄: 30
是否管理员: true
角色:
  - user
  - admin
地址:
  城市: New York
  邮编: 10001

6. 企业级进阶应用场景

6.1 自定义 JSON 编码

场景描述:自定义类型的 JSON 编码行为

使用方法

  1. 实现 json.Marshaler 接口
  2. 自定义编码逻辑
  3. 使用自定义类型

示例代码

go
import (
    "encoding/json"
    "fmt"
    "time"
)

// 自定义时间类型
type CustomTime time.Time

// 实现 Marshaler 接口
func (ct CustomTime) MarshalJSON() ([]byte, error) {
    t := time.Time(ct)
    return json.Marshal(t.Format("2006-01-02 15:04:05"))
}

// 定义结构体
type Event struct {
    Name      string      `json:"name"`
    Timestamp CustomTime  `json:"timestamp"`
}

func main() {
    // 创建事件
    event := Event{
        Name:      "User Login",
        Timestamp: CustomTime(time.Now()),
    }
    
    // 编码为 JSON
    jsonData, err := json.Marshal(event)
    if err != nil {
        fmt.Println("编码失败:", err)
        return
    }
    
    fmt.Println("JSON 数据:", string(jsonData))
}

运行结果

JSON 数据: {"name":"User Login","timestamp":"2024-01-01 12:00:00"}

6.2 自定义 JSON 解码

场景描述:自定义类型的 JSON 解码行为

使用方法

  1. 实现 json.Unmarshaler 接口
  2. 自定义解码逻辑
  3. 使用自定义类型

示例代码

go
import (
    "encoding/json"
    "fmt"
    "time"
)

// 自定义时间类型
type CustomTime time.Time

// 实现 Unmarshaler 接口
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    
    t, err := time.Parse("2006-01-02 15:04:05", s)
    if err != nil {
        return err
    }
    
    *ct = CustomTime(t)
    return nil
}

// 定义结构体
type Event struct {
    Name      string      `json:"name"`
    Timestamp CustomTime  `json:"timestamp"`
}

func main() {
    // JSON 字符串
    jsonData := `{"name":"User Login","timestamp":"2024-01-01 12:00:00"}`
    
    // 解码为结构体
    var event Event
    if err := json.Unmarshal([]byte(jsonData), &event); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    fmt.Println("事件名称:", event.Name)
    fmt.Println("时间戳:", time.Time(event.Timestamp))
}

运行结果

事件名称: User Login
时间戳: 2024-01-01 12:00:00 +0000 UTC

6.3 大文件处理

场景描述:处理大型 JSON 文件

使用方法

  1. 使用 json.Decoder 流式解码
  2. 分块处理数据
  3. 避免一次性加载全部数据

示例代码

go
import (
    "encoding/json"
    "fmt"
    "os"
)

// 定义结构体
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Email string `json:"email"`
}

func main() {
    // 打开文件
    file, err := os.Open("users.json")
    if err != nil {
        fmt.Println("打开文件失败:", err)
        return
    }
    defer file.Close()
    
    // 创建解码器
    decoder := json.NewDecoder(file)
    
    // 解码数组开始
    if _, err := decoder.Token(); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    // 逐个解码用户
    var count int
    for decoder.More() {
        var user User
        if err := decoder.Decode(&user); err != nil {
            fmt.Println("解码用户失败:", err)
            continue
        }
        
        // 处理用户数据
        fmt.Printf("用户 #%d: %s <%s>\n", user.ID, user.Name, user.Email)
        count++
        
        // 每处理 1000 个用户打印一次
        if count%1000 == 0 {
            fmt.Printf("已处理 %d 个用户\n", count)
        }
    }
    
    // 解码数组结束
    if _, err := decoder.Token(); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    fmt.Printf("总共处理 %d 个用户\n", count)
}

运行结果

用户 #1: John <john@example.com>
用户 #2: Alice <alice@example.com>
用户 #3: Bob <bob@example.com>
...
已处理 1000 个用户
...
总共处理 10000 个用户

6.4 JSON 验证

场景描述:验证 JSON 数据的有效性

使用方法

  1. 使用 json.Valid 验证 JSON 格式
  2. 结合结构体标签进行验证
  3. 使用第三方库进行更复杂的验证

示例代码

go
import (
    "encoding/json"
    "fmt"
)

// 定义结构体
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
}

func main() {
    // 验证 JSON 格式
    jsonData1 := `{"id":1,"name":"John","email":"john@example.com"}`
    jsonData2 := `{"id":2,"name":"Alice"}` // 缺少 email
    jsonData3 := `{"id":3,"name":"Bob","email":"invalid-email"}` // 无效邮箱
    
    // 验证 JSON 格式
    fmt.Println("JSON 1 格式有效:", json.Valid([]byte(jsonData1)))
    fmt.Println("JSON 2 格式有效:", json.Valid([]byte(jsonData2)))
    fmt.Println("JSON 3 格式有效:", json.Valid([]byte(jsonData3)))
    
    // 解码并验证数据
    var user User
    if err := json.Unmarshal([]byte(jsonData1), &user); err != nil {
        fmt.Println("JSON 1 解码失败:", err)
    } else {
        fmt.Println("JSON 1 解码成功:", user)
    }
    
    if err := json.Unmarshal([]byte(jsonData2), &user); err != nil {
        fmt.Println("JSON 2 解码失败:", err)
    } else {
        fmt.Println("JSON 2 解码成功:", user)
    }
    
    if err := json.Unmarshal([]byte(jsonData3), &user); err != nil {
        fmt.Println("JSON 3 解码失败:", err)
    } else {
        fmt.Println("JSON 3 解码成功:", user)
    }
}

运行结果

JSON 1 格式有效: true
JSON 2 格式有效: true
JSON 3 格式有效: true
JSON 1 解码成功: {1 John john@example.com}
JSON 2 解码成功: {2 Alice }
JSON 3 解码成功: {3 Bob invalid-email}

6.5 JSON 与数据库交互

场景描述:在数据库中存储和检索 JSON 数据

使用方法

  1. 使用支持 JSON 的数据库(如 PostgreSQL、MongoDB)
  2. 将 JSON 数据存储为字符串或 JSON 类型
  3. 进行查询和索引

示例代码

go
import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/lib/pq"
)

// 定义结构体
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Data map[string]interface{} `json:"data"`
}

func main() {
    // 连接数据库
    db, err := sql.Open("postgres", "postgres://user:password@localhost/dbname?sslmode=disable")
    if err != nil {
        fmt.Println("连接数据库失败:", err)
        return
    }
    defer db.Close()
    
    // 创建表
    _, err = db.Exec(`
        CREATE TABLE IF NOT EXISTS users (
            id SERIAL PRIMARY KEY,
            name VARCHAR(100),
            data JSONB
        )
    `)
    if err != nil {
        fmt.Println("创建表失败:", err)
        return
    }
    
    // 插入数据
    user := User{
        Name: "John",
        Data: map[string]interface{}{
            "age": 30,
            "email": "john@example.com",
            "roles": []string{"user", "admin"},
        },
    }
    
    dataJSON, err := json.Marshal(user.Data)
    if err != nil {
        fmt.Println("编码数据失败:", err)
        return
    }
    
    _, err = db.Exec("INSERT INTO users (name, data) VALUES ($1, $2)", user.Name, dataJSON)
    if err != nil {
        fmt.Println("插入数据失败:", err)
        return
    }
    
    // 查询数据
    rows, err := db.Query("SELECT id, name, data FROM users WHERE name = $1", "John")
    if err != nil {
        fmt.Println("查询数据失败:", err)
        return
    }
    defer rows.Close()
    
    for rows.Next() {
        var id int
        var name string
        var dataJSON []byte
        
        if err := rows.Scan(&id, &name, &dataJSON); err != nil {
            fmt.Println("扫描数据失败:", err)
            continue
        }
        
        var data map[string]interface{}
        if err := json.Unmarshal(dataJSON, &data); err != nil {
            fmt.Println("解码数据失败:", err)
            continue
        }
        
        fmt.Printf("ID: %d, 姓名: %s, 数据: %v\n", id, name, data)
    }
}

运行结果

ID: 1, 姓名: John, 数据: map[age:30 email:john@example.com roles:[user admin]]

7. 行业最佳实践

7.1 结构体设计

实践内容:合理设计结构体用于 JSON 处理

推荐理由

  • 良好的结构体设计可以提高 JSON 处理的效率
  • 使代码更清晰易读
  • 减少错误和 bug

实践方法

  1. 为每个 JSON 结构定义专门的结构体
  2. 使用合适的字段类型
  3. 合理使用结构体标签
  4. 考虑零值处理

示例代码

go
// 好的结构体设计
type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email,omitempty"`
    Age       int       `json:"age,omitempty"`
    CreatedAt time.Time `json:"created_at"`
    Roles     []string  `json:"roles,omitempty"`
}

// 不好的结构体设计
type BadUser struct {
    id        int       // 非导出字段
    Name      string    // 无标签
    Email     string    // 无标签
    age       int       // 非导出字段
    CreatedAt time.Time // 无标签
    roles     []string  // 非导出字段
}

7.2 性能优化

实践内容:优化 JSON 处理性能

推荐理由

  • 提高应用程序的响应速度
  • 减少内存使用
  • 处理更大的数据集

实践方法

  1. 对于大文件使用流式处理
  2. 避免不必要的内存分配
  3. 合理使用指针类型
  4. 缓存常用的 JSON 数据
  5. 使用 json.RawMessage 延迟解析

示例代码

go
// 使用流式处理大文件
func processLargeJSON(filePath string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    decoder := json.NewDecoder(file)
    // 处理数据...
    return nil
}

// 使用 json.RawMessage 延迟解析
type Event struct {
    Type    string          `json:"type"`
    Payload json.RawMessage `json:"payload"`
}

func processEvent(event Event) error {
    switch event.Type {
    case "user.created":
        var user User
        return json.Unmarshal(event.Payload, &user)
    case "order.created":
        var order Order
        return json.Unmarshal(event.Payload, &order)
    default:
        return fmt.Errorf("未知事件类型: %s", event.Type)
    }
}

7.3 错误处理

实践内容:完善的 JSON 错误处理

推荐理由

  • 提高应用程序的可靠性
  • 便于问题定位和调试
  • 提供更好的用户体验

实践方法

  1. 检查所有 JSON 操作的错误
  2. 提供详细的错误信息
  3. 处理不同类型的 JSON 错误
  4. 实现错误恢复机制

示例代码

go
func decodeJSON(data []byte, v interface{}) error {
    if !json.Valid(data) {
        return fmt.Errorf("无效的 JSON 格式")
    }
    
    if err := json.Unmarshal(data, v); err != nil {
        switch e := err.(type) {
        case *json.SyntaxError:
            return fmt.Errorf("JSON 语法错误 at offset %d: %v", e.Offset, e.Error())
        case *json.UnmarshalTypeError:
            return fmt.Errorf("JSON 类型错误 at field %s: %v", e.Field, e.Error())
        default:
            return fmt.Errorf("JSON 解码错误: %v", err)
        }
    }
    
    return nil
}

7.4 安全性

实践内容:确保 JSON 处理的安全性

推荐理由

  • 防止安全漏洞
  • 保护用户数据
  • 符合安全规范

实践方法

  1. 验证 JSON 数据大小
  2. 防止 JSON 注入
  3. 处理敏感数据
  4. 验证输入数据

示例代码

go
// 限制 JSON 数据大小
func safeDecodeJSON(data []byte, v interface{}) error {
    const maxSize = 10 * 1024 * 1024 // 10MB
    if len(data) > maxSize {
        return fmt.Errorf("JSON 数据过大")
    }
    
    return json.Unmarshal(data, v)
}

// 处理敏感数据
type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Password string `json:"-"` // 不在 JSON 中显示
    Email    string `json:"email"`
}

7.5 测试

实践内容:为 JSON 处理编写测试

推荐理由

  • 确保代码的正确性
  • 捕获回归错误
  • 提高代码质量

实践方法

  1. 测试编码和解码功能
  2. 测试边界情况
  3. 测试错误处理
  4. 测试性能

示例代码

go
import (
    "encoding/json"
    "testing"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func TestJSONMarshal(t *testing.T) {
    user := User{ID: 1, Name: "John"}
    data, err := json.Marshal(user)
    if err != nil {
        t.Fatalf("编码失败: %v", err)
    }
    
    expected := `{"id":1,"name":"John"}`
    if string(data) != expected {
        t.Errorf("编码结果不符合预期: got %s, want %s", string(data), expected)
    }
}

func TestJSONUnmarshal(t *testing.T) {
    data := []byte(`{"id":1,"name":"John"}`)
    var user User
    if err := json.Unmarshal(data, &user); err != nil {
        t.Fatalf("解码失败: %v", err)
    }
    
    if user.ID != 1 || user.Name != "John" {
        t.Errorf("解码结果不符合预期: got %+v, want {ID:1, Name:John}", user)
    }
}

8. 常见问题答疑(FAQ)

8.1 如何处理 JSON 中的 null 值?

问题描述:如何处理 JSON 中的 null 值?

回答内容: 可以使用指针类型或 json.RawMessage 来处理 null 值。指针类型在遇到 null 时会被设置为 nil,而非指针类型会被设置为零值。

示例代码

go
type User struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Email *string `json:"email"` // 使用指针处理 null
}

// JSON: {"id":1,"name":"John","email":null}
// 解码后: User{ID:1, Name:"John", Email:nil}

8.2 如何处理 JSON 中的嵌套结构?

问题描述:如何处理 JSON 中的嵌套结构?

回答内容: 可以使用嵌套结构体或 map[string]interface{} 来处理嵌套结构。对于已知结构,使用嵌套结构体;对于未知结构,使用 map[string]interface{}

示例代码

go
type Address struct {
    City  string `json:"city"`
    Zip   int    `json:"zip"`
}

type User struct {
    ID      int     `json:"id"`
    Name    string  `json:"name"`
    Address Address `json:"address"` // 嵌套结构体
}

8.3 如何处理 JSON 中的数组?

问题描述:如何处理 JSON 中的数组?

回答内容: 可以使用切片类型来处理 JSON 数组。对于已知类型的数组,使用具体类型的切片;对于未知类型的数组,使用 []interface{}

示例代码

go
type User struct {
    ID    int      `json:"id"`
    Name  string    `json:"name"`
    Roles []string  `json:"roles"` // 字符串数组
}

// JSON: {"id":1,"name":"John","roles":["user","admin"]}
// 解码后: User{ID:1, Name:"John", Roles:[]string{"user","admin"}}

8.4 如何自定义 JSON 字段名?

问题描述:如何自定义 JSON 字段名?

回答内容: 可以使用结构体标签来自定义 JSON 字段名。标签格式为 json:"name",其中 name 是自定义的字段名。

示例代码

go
type User struct {
    UserID   int    `json:"id"`      // 自定义为 id
    UserName string `json:"name"`    // 自定义为 name
    UserEmail string `json:"email"`  // 自定义为 email
}

// 编码后: {"id":1,"name":"John","email":"john@example.com"}

8.5 如何忽略零值字段?

问题描述:如何在 JSON 编码时忽略零值字段?

回答内容: 可以使用 omitempty 标签来忽略零值字段。标签格式为 json:"name,omitempty"

示例代码

go
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email,omitempty"` // 零值时忽略
}

// 当 Email 为空字符串时
// 编码后: {"id":1,"name":"John"} // 不包含 email 字段

8.6 如何处理大 JSON 文件?

问题描述:如何处理大 JSON 文件?

回答内容: 对于大 JSON 文件,应该使用流式处理,避免一次性加载全部数据到内存。可以使用 json.Decoder 进行流式解码。

示例代码

go
func processLargeJSON(filePath string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    decoder := json.NewDecoder(file)
    // 处理数据...
    return nil
}

9. 实战练习

9.1 基础练习:JSON 编码和解码

解题思路

  1. 定义结构体
  2. 实现 JSON 编码
  3. 实现 JSON 解码
  4. 测试编码和解码结果

常见误区

  • 结构体字段未导出(首字母小写)
  • 标签使用错误
  • 类型不匹配

分步提示

  1. 定义包含不同类型字段的结构体
  2. 添加适当的 JSON 标签
  3. 使用 json.Marshal 编码
  4. 使用 json.Unmarshal 解码
  5. 验证编码和解码结果

参考代码

go
import (
    "encoding/json"
    "fmt"
    "time"
)

// 定义结构体
type Product struct {
    ID          int       `json:"id"`
    Name        string    `json:"name"`
    Price       float64   `json:"price"`
    InStock     bool      `json:"in_stock"`
    Tags        []string  `json:"tags,omitempty"`
    Description string    `json:"description,omitempty"`
    CreatedAt   time.Time `json:"created_at"`
}

func main() {
    // 创建产品实例
    product := Product{
        ID:          1,
        Name:        "Laptop",
        Price:       999.99,
        InStock:     true,
        Tags:        []string{"electronics", "computer"},
        Description: "High-performance laptop",
        CreatedAt:   time.Now(),
    }
    
    // 编码为 JSON
    jsonData, err := json.MarshalIndent(product, "", "  ")
    if err != nil {
        fmt.Println("编码失败:", err)
        return
    }
    fmt.Println("编码结果:")
    fmt.Println(string(jsonData))
    
    // 解码为结构体
    var decodedProduct Product
    if err := json.Unmarshal(jsonData, &decodedProduct); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    fmt.Println("\n解码结果:")
    fmt.Printf("ID: %d\n", decodedProduct.ID)
    fmt.Printf("Name: %s\n", decodedProduct.Name)
    fmt.Printf("Price: %.2f\n", decodedProduct.Price)
    fmt.Printf("InStock: %v\n", decodedProduct.InStock)
    fmt.Printf("Tags: %v\n", decodedProduct.Tags)
    fmt.Printf("Description: %s\n", decodedProduct.Description)
    fmt.Printf("CreatedAt: %v\n", decodedProduct.CreatedAt)
}

运行结果

编码结果:
{
  "id": 1,
  "name": "Laptop",
  "price": 999.99,
  "in_stock": true,
  "tags": [
    "electronics",
    "computer"
  ],
  "description": "High-performance laptop",
  "created_at": "2024-01-01T12:00:00Z"
}

解码结果:
ID: 1
Name: Laptop
Price: 999.99
InStock: true
Tags: [electronics computer]
Description: High-performance laptop
CreatedAt: 2024-01-01 12:00:00 +0000 UTC

9.2 进阶练习:自定义 JSON 处理

解题思路

  1. 创建自定义类型
  2. 实现 json.Marshaler 接口
  3. 实现 json.Unmarshaler 接口
  4. 测试自定义类型的 JSON 处理

常见误区

  • 接口实现错误
  • 类型转换错误
  • 错误处理不完善

分步提示

  1. 定义自定义类型(如自定义时间类型)
  2. 实现 MarshalJSON 方法
  3. 实现 UnmarshalJSON 方法
  4. 在结构体中使用自定义类型
  5. 测试编码和解码

参考代码

go
import (
    "encoding/json"
    "fmt"
    "time"
)

// 自定义日期类型
type CustomDate time.Time

// 实现 Marshaler 接口
func (cd CustomDate) MarshalJSON() ([]byte, error) {
    t := time.Time(cd)
    return json.Marshal(t.Format("2006-01-02"))
}

// 实现 Unmarshaler 接口
func (cd *CustomDate) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    
    t, err := time.Parse("2006-01-02", s)
    if err != nil {
        return err
    }
    
    *cd = CustomDate(t)
    return nil
}

// 定义结构体
type Event struct {
    ID        int         `json:"id"`
    Name      string      `json:"name"`
    Date      CustomDate  `json:"date"`
    Location  string      `json:"location"`
}

func main() {
    // 创建事件实例
    event := Event{
        ID:        1,
        Name:      "Conference",
        Date:      CustomDate(time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC)),
        Location:  "New York",
    }
    
    // 编码为 JSON
    jsonData, err := json.MarshalIndent(event, "", "  ")
    if err != nil {
        fmt.Println("编码失败:", err)
        return
    }
    fmt.Println("编码结果:")
    fmt.Println(string(jsonData))
    
    // 解码为结构体
    var decodedEvent Event
    if err := json.Unmarshal(jsonData, &decodedEvent); err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    fmt.Println("\n解码结果:")
    fmt.Printf("ID: %d\n", decodedEvent.ID)
    fmt.Printf("Name: %s\n", decodedEvent.Name)
    fmt.Printf("Date: %v\n", time.Time(decodedEvent.Date))
    fmt.Printf("Location: %s\n", decodedEvent.Location)
}

运行结果

编码结果:
{
  "id": 1,
  "name": "Conference",
  "date": "2024-12-25",
  "location": "New York"
}

解码结果:
ID: 1
Name: Conference
Date: 2024-12-25 00:00:00 +0000 UTC
Location: New York

9.3 挑战练习:JSON 数据处理 pipeline

解题思路

  1. 设计 JSON 处理 pipeline
  2. 实现数据读取、处理和写入
  3. 处理错误和边界情况
  4. 测试 pipeline 性能

常见误区

  • 内存使用过高
  • 错误处理不完善
  • 性能优化不足

分步提示

  1. 从文件读取 JSON 数据
  2. 解析 JSON 数据
  3. 处理数据(如过滤、转换)
  4. 编码处理后的数据
  5. 写入到新文件
  6. 测试整个 pipeline

参考代码

go
import (
    "encoding/json"
    "fmt"
    "os"
    "time"
)

// 定义输入数据结构
type InputUser struct {
    ID        int    `json:"id"`
    Name      string `json:"name"`
    Email     string `json:"email"`
    Age       int    `json:"age"`
    IsActive  bool   `json:"is_active"`
}

// 定义输出数据结构
type OutputUser struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    Age       int       `json:"age"`
    IsActive  bool      `json:"is_active"`
    Processed bool      `json:"processed"`
    Timestamp time.Time `json:"processed_at"`
}

func processJSON(inputPath, outputPath string) error {
    // 打开输入文件
    inputFile, err := os.Open(inputPath)
    if err != nil {
        return fmt.Errorf("打开输入文件失败: %w", err)
    }
    defer inputFile.Close()
    
    // 创建输出文件
    outputFile, err := os.Create(outputPath)
    if err != nil {
        return fmt.Errorf("创建输出文件失败: %w", err)
    }
    defer outputFile.Close()
    
    // 创建解码器和编码器
    decoder := json.NewDecoder(inputFile)
    encoder := json.NewEncoder(outputFile)
    encoder.SetIndent("", "  ")
    
    // 解码数组开始
    if _, err := decoder.Token(); err != nil {
        return fmt.Errorf("解码数组开始失败: %w", err)
    }
    
    // 写入数组开始
    if err := encoder.Encode([]OutputUser{}); err != nil {
        return fmt.Errorf("写入数组开始失败: %w", err)
    }
    
    // 处理每个用户
    var count int
    for decoder.More() {
        var inputUser InputUser
        if err := decoder.Decode(&inputUser); err != nil {
            return fmt.Errorf("解码用户失败: %w", err)
        }
        
        // 处理用户数据
        outputUser := OutputUser{
            ID:        inputUser.ID,
            Name:      inputUser.Name,
            Email:     inputUser.Email,
            Age:       inputUser.Age,
            IsActive:  inputUser.IsActive,
            Processed: true,
            Timestamp: time.Now(),
        }
        
        // 编码用户数据
        if err := encoder.Encode(outputUser); err != nil {
            return fmt.Errorf("编码用户失败: %w", err)
        }
        
        count++
        if count%1000 == 0 {
            fmt.Printf("已处理 %d 个用户\n", count)
        }
    }
    
    // 解码数组结束
    if _, err := decoder.Token(); err != nil {
        return fmt.Errorf("解码数组结束失败: %w", err)
    }
    
    fmt.Printf("总共处理 %d 个用户\n", count)
    return nil
}

func main() {
    inputPath := "users.json"
    outputPath := "processed_users.json"
    
    start := time.Now()
    if err := processJSON(inputPath, outputPath); err != nil {
        fmt.Println("处理失败:", err)
        return
    }
    duration := time.Since(start)
    
    fmt.Printf("处理完成,耗时: %v\n", duration)
}

运行结果

已处理 1000 个用户
已处理 2000 个用户
已处理 3000 个用户
...
总共处理 10000 个用户
处理完成,耗时: 1.234s

10. 知识点总结

10.1 核心要点

  • JSON 基本概念:对象、数组、字符串、数字、布尔值、null
  • Go 语言 JSON 处理Marshal/Unmarshal 用于基本处理,Encoder/Decoder 用于流式处理
  • 结构体标签:使用 json:"name,option" 控制编码和解码行为
  • 自定义类型:实现 MarshalerUnmarshaler 接口自定义 JSON 处理
  • 性能优化:使用流式处理、json.RawMessage、合理的结构体设计
  • 错误处理:检查错误返回值,处理不同类型的 JSON 错误

10.2 易错点回顾

  • 字段导出:结构体字段必须首字母大写才能被 JSON 处理
  • 标签使用:标签语法错误会导致字段处理不符合预期
  • 类型匹配:JSON 数据类型与 Go 类型不匹配会导致解码失败
  • 大文件处理:直接使用 Marshal/Unmarshal 处理大文件会导致内存问题
  • 时间处理:时间格式不符合标准会导致编码/解码失败
  • null 值处理:非指针类型遇到 null 会被设置为零值

11. 拓展参考资料

11.1 官方文档链接

11.2 进阶学习路径建议

  • JSON Schema:学习 JSON Schema 用于数据验证
  • Protocol Buffers:学习 Protocol Buffers 作为 JSON 的替代方案
  • MessagePack:学习 MessagePack 作为更高效的序列化格式
  • GraphQL:学习 GraphQL 作为 API 数据交换的替代方案
  • 数据压缩:学习如何压缩 JSON 数据以减少传输大小

11.3 相关工具推荐

  • jq:命令行 JSON 处理工具
  • JSON Viewer:浏览器扩展,用于查看和格式化 JSON
  • go-json:比标准库更快的 JSON 处理库
  • easyjson:基于代码生成的高性能 JSON 处理库
  • validator:用于验证 JSON 数据的库