【Go语言学习系列17】标准库探索(四):JSON处理

📚 原创系列: “Go语言学习系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。

📑 Go语言学习系列导航

本文是【Go语言学习系列】的第17篇,当前位于第二阶段(基础巩固篇)

🚀 第二阶段:基础巩固篇
  1. 13-包管理深入理解
  2. 14-标准库探索(一):io与文件操作
  3. 15-标准库探索(二):字符串处理
  4. 16-标准库探索(三):时间与日期
  5. 17-标准库探索(四):JSON处理 👈 当前位置
  6. 18-标准库探索(五):HTTP客户端
  7. 19-标准库探索(六):HTTP服务器
  8. 20-单元测试基础
  9. 21-基准测试与性能剖析入门
  10. 22-反射机制基础
  11. 23-Go中的面向对象编程
  12. 24-函数式编程在Go中的应用
  13. 25-context包详解
  14. 26-依赖注入与控制反转
  15. 27-第二阶段项目实战:RESTful API服务

📚 查看完整Go语言学习系列导航

📖 文章导读

在本文中,您将了解:

  • JSON的基本概念及其在Go中的类型映射
  • 使用encoding/json包进行基本序列化与反序列化
  • JSON标签的使用技巧与嵌套结构处理
  • 高效处理大型JSON数据的流式处理方法
  • 自定义JSON编码与解码的高级技术
  • 处理复杂场景如多态JSON和动态字段名
  • JSON处理的性能优化最佳实践

JSON作为一种轻量级数据交换格式,在Web应用、API和配置系统中被广泛使用。Go语言提供了功能完善的JSON处理库,本文将带您全面掌握这一关键技能。

1. JSON基础知识

在深入Go的JSON处理之前,让我们先简要回顾一下JSON的基础知识。

1.1 JSON数据类型

JSON支持以下数据类型:

  • 数字:整数或浮点数,如 1233.14
  • 字符串:用双引号包围的文本,如 "Hello, World"
  • 布尔值truefalse
  • 数组:有序的值集合,用方括号表示,如 [1, 2, 3]
  • 对象:键值对集合,用花括号表示,如 {"name": "John", "age": 30}
  • null:表示空值,如 {"address": null}

1.2 JSON与Go类型的映射

Go的encoding/json包会根据以下规则在JSON和Go类型之间进行映射:

JSON类型 Go类型
对象 struct, map[string]T
数组 slice, array
字符串 string
数字 int, int8, …, int64, uint, uint8, …, uint64, float32, float64
布尔值 bool
null nil

2. 基本序列化与反序列化

Go提供了简单的函数将Go对象转换为JSON(序列化/Marshal),以及将JSON转换回Go对象(反序列化/Unmarshal)。

2.1 序列化Go结构体到JSON

使用json.Marshal函数可以将Go结构体转换为JSON字节切片:

package main

import (
    "encoding/json"
    "fmt"
)

// 定义一个Person结构体
type Person struct {
   
   
    Name    string   `json:"name"`
    Age     int      `json:"age"`
    Email   string   `json:"email"`
    Hobbies []string `json:"hobbies"`
}

func main() {
   
   
    // 创建一个Person实例
    person := Person{
   
   
        Name:    "张三",
        Age:     28,
        Email:   "zhangsan@example.com",
        Hobbies: []string{
   
   "读书", "旅行", "编程"},
    }

    // 序列化为JSON
    jsonData, err := json.Marshal(person)
    if err != nil {
   
   
        fmt.Println("序列化失败:", err)
        return
    }

    // 打印JSON字符串
    fmt.Println(string(jsonData))
}

输出:

{
   
   "name":"张三","age":28,"email":"zhangsan@example.com","hobbies":["读书","旅行","编程"]}

为了使输出的JSON更易读,可以使用json.MarshalIndent添加缩进:

// 带缩进的序列化
jsonIndent, err := json.MarshalIndent(person, "", "  ")
if err != nil {
   
   
    fmt.Println("序列化失败:", err)
    return
}

fmt.Println(string(jsonIndent))

输出:

{
   
   
  "name": "张三",
  "age": 28,
  "email": "zhangsan@example.com",
  "hobbies": [
    "读书",
    "旅行",
    "编程"
  ]
}

2.2 反序列化JSON到Go结构体

使用json.Unmarshal函数可以将JSON字节切片转换为Go结构体:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
   
   
    Name    string   `json:"name"`
    Age     int      `json:"age"`
    Email   string   `json:"email"`
    Hobbies []string `json:"hobbies"`
}

func main() {
   
   
    // JSON字符串
    jsonStr := `{
        "name": "李四",
        "age": 32,
        "email": "lisi@example.com",
        "hobbies": ["摄影", "游泳", "烹饪"]
    }`

    // 创建一个Person变量用于存储反序列化的结果
    var person Person

    // 反序列化
    err := json.Unmarshal([]byte(jsonStr), &person)
    if err != nil {
   
   
        fmt.Println("反序列化失败:", err)
        return
    }

    // 打印结果
    fmt.Printf("姓名: %s\n", person.Name)
    fmt.Printf("年龄: %d\n", person.Age)
    fmt.Printf("邮箱: %s\n", person.Email)
    fmt.Printf("爱好: %v\n", person.Hobbies)
}

输出:

姓名: 李四
年龄: 32
邮箱: lisi@example.com
爱好: [摄影 游泳 烹饪]

2.3 处理未知结构的JSON

在某些情况下,我们可能不知道JSON的确切结构。这时可以使用map[string]interface{}interface{}来解析JSON:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
   
   
    // 一个未知结构的JSON
    jsonStr := `{
        "name": "王五",
        "details": {
            "city": "北京",
            "postal_code": "100000"
        },
        "scores": [95, 89, 75],
        "graduated": true
    }`

    // 解析为map[string]interface{}
    var result map[string]interface{
   
   }
    err := json.Unmarshal([]byte(jsonStr), &result)
    if err != nil {
   
   
        fmt.Println("反序列化失败:", err)
        return
    }

    // 访问反序列化后的数据
    fmt.Println("姓名:", result["name"])
    
    // 获取嵌套字段需要类型断言
    details := result["details"].(map[string]interface{
   
   })
    fmt.Println("城市:", details["city"])
    fmt.Println("邮编:", details["postal_code"])
    
    // 获取数组
    scores := result["scores"].([]interface{
   
   })
    fmt.Printf("成绩: %.0f %.0f %.0f\n", scores[0], scores[1], scores[2])
    
    // 获取布尔值
    graduated := result["graduated"].(bool)
    fmt.Println("是否毕业:", graduated)
}

输出:

姓名: 王五
城市: 北京
邮编: 100000
成绩: 95 89 75
是否毕业: true

2.4 序列化与反序列化Map

除了结构体,我们还可以直接序列化和反序列化Map:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
   
   
    // 创建一个map
    userMap := map[string]interface{
   
   }{
   
   
        "name":     "赵六",
        "age":      42,
        "is_admin": true,
        "contact": map[string]string{
   
   
            "phone": "13812345678",
            "email": "zhaoliu@example.com",
        },
    }

    // 序列化map
    jsonData, err := json.MarshalIndent(userMap, "", "  ")
    if err != nil {
   
   
        fmt.Println("序列化失败:", err)
        return
    }

    fmt.Println("序列化结果:")
    fmt.Println(string(jsonData))

    // 反序列化回map
    var newUserMap map[string]interface{
   
   }
    err = json.Unmarshal(jsonData, &newUserMap)
    if err != nil {
   
   
        fmt.Println("反序列化失败:", err)
        return
    }

    fmt.Println("\n反序列化结果:")
    fmt.Printf("姓名: %s\n", newUserMap["name"])
    fmt.Printf("年龄: %.0f\n", newUserMap["age"])
    fmt.Printf("是否管理员: %t\n", newUserMap["is_admin"])
    
    contact := newUserMap["contact"].(map[string]interface{
   
   })
    fmt.Printf("电话: %s\n", contact["phone"])
    fmt.Printf("邮箱: %s\n", contact["email"])
}

输出:

序列化结果:
{
  "age": 42,
  "contact": {
    "email": "zhaoliu@example.com",
    "phone": "13812345678"
  },
  "is_admin": true,
  "name": "赵六"
}

反序列化结果:
姓名: 赵六
年龄: 42
是否管理员: true
电话: 13812345678
邮箱: zhaoliu@example.com

3. JSON标签

Go使用结构体标签来控制字段如何序列化和反序列化。这些标签提供了强大的自定义能力。

3.1 基本JSON标签

type User struct {
   
   
    UserName  string `json:"username"`      // 重命名字段
    Password  string `json:"-"`             // 完全忽略该字段
    Email     string `json:"email,omitempty"` // 如果为零值则忽略
    CreatedAt time.Time `json:"created_at"` // 重命名并保留大小写
}

标签选项说明:

  • json:"fieldname" - 设置JSON中的字段名(默认使用结构体字段名)
  • json:"-" - 在序列化时忽略此字段
  • json:",omitempty" - 如果字段为空值(零值),则不包含在JSON输出中
  • json:"fieldname,omitempty" - 组合使用重命名和omitempty

示例:

package main

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

type User struct {
   
   
    UserName  string    `json:"username"`
    Password  string    `json:"-"`
    Email     string    `json:"email,omitempty"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at,omitempty"`
}

func main() {
   
   
    // 创建一个User实例
    user := User{
   
   
        UserName:  "gopher123",
        Password:  "secret123",
        Email:     "", // 空字符串是string的零值
        CreatedAt: time.Now(),
        // UpdatedAt未设置,将是零值
    }

    // 序列化
    data, _ := json.MarshalIndent(user, "", "  ")
    fmt.Println(string(data))
}

输出:

{
   
   
  "username": "gopher123",
  "created_at": "2023-05-25T15:30:45.123456789+08:00"
}

注意:

  • Password字段被完全忽略
  • Email由于是零值且有omitempty标签,所以不出现在JSON中
  • UpdatedAt是零时间且有omitempty标签,所以不出现在JSON中

3.2 自定义字段名

除了基本的重命名,JSON标签还允许使用特殊字符:

type Product struct {
   
   
    ID          int     `json:"id"`
    Name        string  `json:"product-name"`  // 使用连字符
    Price       float64 `json:"price($)"`      // 使用特殊字符
    Description string  `json:"desc.detailed"` // 使用点
}

3.3 处理嵌套结构体

标签也适用于嵌套结构体:

package main

import (
    "encoding/json"
    "fmt"
)

type Address struct {
   
   
    Street  string `json:"street"`
    City    string `json:"city"`
    Country string `json:"country"`
}

type Employee struct {
   
   
    Name    string  `json:"name"`
    Age     int     `json:"age,omitempty"`
    Address Address `json:"address"` // 嵌套结构体
}

func main() {
   
   
    emp := Employee{
   
   
        Name: "李明",
        Age:  0, // 将被忽略
        Address: Address{
   
   
            Street:  "朝阳路123号",
            City:    "北京",
            Country: "中国",
        },
    }

    data
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值