Golang通过encoding/json包实现JSON序列化与反序列化,核心是json.Marshal和json.Unmarshal,需关注结构体标签、类型匹配及错误处理。使用json:"name"标签映射字段,omitempty忽略空值,-忽略字段,复合标签如json:"age,string,omitempty"支持类型转换。处理命名不一致或特殊类型时,可实现自定义MarshalJSON和UnmarshalJSON方法,如Unix时间戳与time.Time互转。面对嵌套JSON,应精确定义多层结构体,动态结构可用map[string]interface{}并配合类型断言;推荐使用json.RawMessage延迟解析复杂子结构。为提升性能,尤其在大数据量或高并发场景,宜采用json.Encoder和json.Decoder进行流式处理,减少内存占用,避免一次性加载全部数据。同时注意数字类型精度问题,优先使用float64或json.Number。最佳实践包括:始终检查错误、合理使用omitempty、避免过度使用interface{}、复用结构体实例以减少GC压力,并在确认性能瓶颈后考虑引入jsoniter等高效第三方库。

Golang处理JSON的核心在于
encoding/json
json.Marshal
json.Unmarshal
在Go语言中,处理JSON序列化与反序列化,我们通常会定义一个结构体来映射JSON的字段。
package main
import (
"encoding/json"
"fmt"
"log"
)
// User 定义一个用户结构体,用于JSON的序列化和反序列化
type User struct {
ID int `json:"id"` // 字段ID映射到JSON的"id"
Name string `json:"name"` // 字段Name映射到JSON的"name"
Email string `json:"email,omitempty"` // 字段Email映射到JSON的"email",如果为空则忽略
IsActive bool `json:"is_active"` // 字段IsActive映射到JSON的"is_active"
Roles []string `json:"roles,omitempty"` // 字段Roles映射到JSON的"roles",如果为空则忽略
CreatedAt string `json:"-"` // 字段CreatedAt在JSON中被忽略
Age int `json:"age,string,omitempty"` // 字段Age映射到JSON的"age",序列化为字符串,空则忽略
}
func main() {
// --- 序列化 (Marshal) ---
user := User{
ID: 1,
Name: "张三",
Email: "zhangsan@example.com",
IsActive: true,
Roles: []string{"admin", "user"},
CreatedAt: "2023-01-01", // 这个字段不会被序列化
Age: 30,
}
jsonData, err := json.Marshal(user)
if err != nil {
log.Fatalf("序列化失败: %v", err)
}
fmt.Println("序列化结果 (带Email):", string(jsonData))
// 尝试一个Email为空的用户,看看omitempty的效果
userNoEmail := User{
ID: 2,
Name: "李四",
IsActive: false,
Roles: []string{"guest"},
CreatedAt: "2023-02-01",
Age: 25,
}
jsonDataNoEmail, err := json.Marshal(userNoEmail)
if err != nil {
log.Fatalf("序列化失败 (无Email): %v", err)
}
fmt.Println("序列化结果 (无Email):", string(jsonDataNoEmail))
// --- 反序列化 (Unmarshal) ---
jsonString := `{"id":101,"name":"王五","email":"wangwu@example.com","is_active":true,"roles":["editor"],"age":"40"}`
var newUser User
err = json.Unmarshal([]byte(jsonString), &newUser)
if err != nil {
log.Fatalf("反序列化失败: %v", err)
}
fmt.Printf("反序列化结果: %+v\n", newUser)
// 尝试反序列化一个缺失字段的JSON
jsonStringMissingField := `{"id":102,"name":"赵六","is_active":false}`
var userMissingField User
err = json.Unmarshal([]byte(jsonStringMissingField), &userMissingField)
if err != nil {
log.Fatalf("反序列化失败 (缺失字段): %v", err)
}
fmt.Printf("反序列化结果 (缺失字段): %+v\n", userMissingField)
}这段代码展示了最基础的JSON处理流程。
json:"tag"
omitempty
json:"-"
json:"age,string,omitempty"
Age
Go语言的JSON处理,尤其是命名约定和类型转换,我觉得是初学者经常会遇到“坑”的地方。默认情况下,
encoding/json
json:"field_name"
立即学习“go语言免费学习笔记(深入)”;
比如,Go里有个字段叫
CreatedAt
created_at
json:"created_at"
更复杂一点的,是类型转换。有时候JSON里的某个字段是字符串,但你希望在Go里把它解析成数字,或者反过来。比如JSON里
"price": "19.99"
float64
json
这时候,自定义
MarshalJSON
UnmarshalJSON
time.Time
package main
import (
"encoding/json"
"fmt"
"strconv"
"time"
)
// UnixTime 自定义一个时间类型,用于处理Unix时间戳
type UnixTime time.Time
// MarshalJSON 实现 json.Marshaler 接口
func (t UnixTime) MarshalJSON() ([]byte, error) {
// 将时间转换为Unix时间戳字符串
return []byte(strconv.FormatInt(time.Time(t).Unix(), 10)), nil
}
// UnmarshalJSON 实现 json.Unmarshaler 接口
func (t *UnixTime) UnmarshalJSON(data []byte) error {
// 将JSON中的数字(字符串形式或直接数字)解析为Unix时间戳
// 允许JSON中是数字或字符串形式的数字
s := string(data)
if s == "null" { // 处理JSON null值
return nil
}
timestamp, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf("无法解析Unix时间戳: %w", err)
}
*t = UnixTime(time.Unix(timestamp, 0))
return nil
}
type Event struct {
ID string `json:"id"`
Timestamp UnixTime `json:"timestamp"` // 使用自定义的UnixTime
Name string `json:"name"`
}
func main() {
// 序列化
event := Event{
ID: "evt-001",
Timestamp: UnixTime(time.Now()),
Name: "Meeting Start",
}
eventData, err := json.Marshal(event)
if err != nil {
fmt.Printf("序列化失败: %v\n", err)
return
}
fmt.Println("序列化后的事件:", string(eventData))
// 反序列化
jsonStr := `{"id":"evt-002","timestamp":1678886400,"name":"Project Deadline"}` // 假设1678886400是2023-03-15 00:00:00 UTC
var newEvent Event
err = json.Unmarshal([]byte(jsonStr), &newEvent)
if err != nil {
fmt.Printf("反序列化失败: %v\n", err)
return
}
fmt.Printf("反序列化后的事件: %+v, 时间: %s\n", newEvent, time.Time(newEvent.Timestamp).Format(time.RFC3339))
// 尝试一个字符串形式的数字时间戳
jsonStr2 := `{"id":"evt-003","timestamp":"1678886400","name":"Another Event"}`
var newEvent2 Event
err = json.Unmarshal([]byte(jsonStr2), &newEvent2)
if err != nil {
fmt.Printf("反序列化失败 (字符串时间戳): %v\n", err)
return
}
fmt.Printf("反序列化后的事件2: %+v, 时间: %s\n", newEvent2, time.Time(newEvent2.Timestamp).Format(time.RFC3339))
}通过这种方式,我们不仅解决了命名问题,还灵活地处理了数据类型在Go和JSON之间不完全一致的情况。这在处理一些老旧API或者第三方服务返回的奇特JSON格式时,尤其有用。
处理复杂或嵌套的JSON结构,说实话,挺考验耐心和设计能力的。最常见的陷阱就是结构体定义与JSON结构不匹配,导致解析失败或者数据丢失。
比如,JSON里有一个深层嵌套的对象数组:
{
"order_id": "ORD-2023001",
"customer": {
"id": 1001,
"name": "Alice"
},
"items": [
{
"item_id": "A1",
"name": "Laptop",
"price": 1200.00,
"details": {
"color": "silver",
"weight_kg": 1.5
}
},
{
"item_id": "B2",
"name": "Mouse",
"price": 25.00,
"details": {
"wireless": true
}
}
]
}要解析这样的JSON,你的Go结构体也必须是多层嵌套的:
type Order struct {
OrderID string `json:"order_id"`
Customer Customer `json:"customer"`
Items []Item `json:"items"`
}
type Customer struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Item struct {
ItemID string `json:"item_id"`
Name string `json:"name"`
Price float64 `json:"price"`
Details map[string]interface{} `json:"details"` // 注意这里使用了map[string]interface{}
}这里有个小细节,
Item
Details
map[string]interface{}Details
Item
color
weight_kg
wireless
Details
interface{}最佳实践我觉得有几点:
map[string]interface{}[]interface{}json.Unmarshal
json.Marshal
error
omitempty
json.RawMessage
json.RawMessage
Unmarshal
number
int
float64
int64
float64
json.Number
在处理大量JSON数据时,性能确实是一个需要关注的点。
encoding/json
一个常见的场景是,你可能需要从网络流中读取JSON数据,或者将大量数据写入网络流。这时候,直接使用
json.Marshal
json.Unmarshal
这时候,
json.Encoder
json.Decoder
io.Reader
io.Writer
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"strings"
"time"
)
type LogEntry struct {
Timestamp time.Time `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Source string `json:"source,omitempty"`
}
func main() {
// --- 使用 json.Encoder 进行流式序列化 ---
fmt.Println("--- Encoder 示例 ---")
entries := []LogEntry{
{Timestamp: time.Now(), Level: "INFO", Message: "User logged in", Source: "auth"},
{Timestamp: time.Now().Add(time.Second), Level: "WARN", Message: "Disk usage high"},
}
// 模拟写入到文件或网络流
var buf strings.Builder
encoder := json.NewEncoder(&buf) // 写入到strings.Builder
encoder.SetIndent("", " ") // 可以设置缩进,让输出更可读
for _, entry := range entries {
if err := encoder.Encode(entry); err != nil {
log.Fatalf("编码日志条目失败: %v", err)
}
}
fmt.Println("编码后的日志流:\n", buf.String())
// --- 使用 json.Decoder 进行流式反序列化 ---
fmt.Println("\n--- Decoder 示例 ---")
jsonStream := `
{"timestamp":"2023-01-01T10:00:00Z","level":"INFO","message":"Service started"}
{"timestamp":"2023-01-01T10:01:00Z","level":"ERROR","message":"Database connection failed","source":"db"}
{"timestamp":"2023-01-01T10:02:00Z","level":"DEBUG","message":"Processing request"}
`
reader := strings.NewReader(jsonStream)
decoder := json.NewDecoder(reader)
for {
var entry LogEntry
err := decoder.Decode(&entry)
if err == io.EOF { // 读取到流末尾
break
}
if err != nil {
log.Fatalf("解码日志条目失败: %v", err)
}
fmt.Printf("解码日志: %+v\n", entry)
}
// 另一个性能考虑点是,如果你的JSON结构非常复杂,并且有些部分你根本不关心,
// 那么解析到 map[string]interface{} 或者只定义部分结构体,然后手动提取感兴趣的字段,
// 可能会比完整解析整个大结构体要快。
// 但这通常以代码可读性和类型安全性为代价,需要权衡。
// 对于极致性能要求,可以考虑第三方库,比如 "jsoniter"。
// 它通常比标准库快2-3倍,但在大多数应用场景下,标准库的性能已经足够。
// 引入第三方库会增加依赖,也可能带来额外的学习成本和维护负担。
// 所以,我通常会先用标准库,只有当性能瓶颈确实出现在JSON处理上时,才考虑替换。
// 最后,避免不必要的内存分配。
// 如果你反复处理相同类型的JSON,可以复用结构体实例或者切片的底层数组,
// 减少垃圾回收的压力。但这通常是微优化,除非你真的遇到了GC压力。
}使用
Encoder
Decoder
还有一点,关于性能,有时候不是JSON库本身慢,而是你的结构体设计或者数据量太大。比如,如果你有一个非常大的切片或映射需要序列化,预先分配好它们的容量(
make([]Type, 0, capacity)
go test -bench=.
以上就是Golang JSON处理技巧 序列化与反序列化实现的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号