做Go开发这几年,我踩过最多的坑就在JSON序列化和反序列化上。很多同学刚用Go写接口时都会有一个感觉:官方库encoding/json用起来很顺手,几行代码就能把一个结构体变成JSON,也能把一个JSON字符串塞进结构体。但等项目一上线、并发一上来,各种问题就全冒出来了——字段解析不出来、性能扛不住、时间格式不对、日志里天天报failed to deserialize the json body into the target type。这篇文章就是想把Go里JSON处理这件事从基础到实战彻底聊透,把我自己试过、踩过、优化过的东西都整理出来,希望能帮大家少走点弯路。
我会先讲标准库的正确打开方式,再讲流式处理和性能优化,接着分享我在真实项目里遇到过的坑和安全问题,最后聊聊第三方库怎么选。适用对象很明确:刚开始写Go接口的初级开发者,写过一阵但总被JSON问题困扰的中级开发者,以及需要在微服务、高并发场景里优化JSON处理的后端工程师。
1. 认识Go的JSON处理:为什么这活儿值得好好学
1.1 项目起源:从一次接口对接说起
我记得最早接触Go就是因为一个订单服务要做接口对接。当时对方返回的JSON长这样:
json复制{"order_id": 123456, "user_name": "zhangsan", "amount": 99.9}
我照着写了个结构体:
go复制type Order struct {
OrderId int64
UserName string
Amount float64
}
结果一解析,OrderId和UserName全是零值,Amount也是0。我盯着代码看了半天,明明字段名字都能对上啊,后来才反应过来:结构体字段是首字母大写没错,但JSON的key是下划线风格,标准库默认匹配是大小写不敏感地匹配原始字段名,它可不会自动把order_id转换成OrderId。这就是Go JSON序列化里最基础也最坑的一个点:没有加json tag。
从那之后我就养成了习惯:所有用于JSON传输的结构体,一律显式写json:"xxx",不靠猜。这件事也让我意识到,Go的JSON处理虽然API简单,但细节非常多,值得系统梳理一遍。
1.2 JSON在Go生态里的地位
只要写后端,就绕不开JSON。RESTful接口请求体、响应体、配置中心下发、日志结构化输出、消息队列消息体、数据库里的JSON字段……几乎处处都有JSON。Go在服务端领域用得越来越多,encoding/json也是Go标准库里使用频率最高的包之一。
Go本身是静态强类型语言,JSON又是动态结构,两者之间的转换天然会有摩擦。encoding/json帮我们做了大量反射层面的工作,但“反射”也带来了不少问题:性能相对慢、字段匹配有规则、错误信息有时让人摸不着头脑。而这些恰恰是开发中最高频的痛点。把JSON序列化、反序列化吃透,是Go后端开发的基本功,也是拉开开发效率差距的关键。
1.3 适合谁来学
这篇文章面向的核心人群有三类。第一类是刚接触Go的新手,需要快速学会标准库的序列化和反序列化,知道结构体tag怎么写、数据怎么转换。第二类是已经写了几个月Go的开发者,开始遇到性能问题或者复杂的JSON结构,需要掌握流式解析、自定义MarshalJSON等进阶技能。第三类是在团队里负责技术方案选型或代码Review的老手,可以从工具对比和踩坑部分找到一些参考。
我不会只丢一堆代码片段,会把每个选择背后的原因也说清楚。比如为什么推荐用json.Decoder解析大JSON、为什么自定义时间格式要小心、为什么不能盲目信任外部传进来的JSON字段。这些经验不是从文档里抄来的,是真实项目里一个坑一个坑踩出来的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础实操:encoding/json标准库的序列化与反序列化
2.1 最常用的Marshal与Unmarshal
encoding/json的核心就两个函数:json.Marshal负责把Go对象变成JSON字节切片,json.Unmarshal负责把JSON字节切片变成Go对象。用法非常简单:
go复制package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
u := User{Name: "张三", Age: 18}
// 序列化
data, err := json.Marshal(u)
if err != nil {
panic(err)
}
fmt.Println(string(data)) // {"name":"张三","age":18}
// 反序列化
var u2 User
err = json.Unmarshal(data, &u2)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", u2)
}
这段代码是几乎所有Go后端项目都会出现的“骨架”。但我要强调一个初学者很容易忽略的点:json.Unmarshal的第二个参数必须传指针。如果你写json.Unmarshal(data, u2),它不会报错,但也不会把数据填进去,你会得到一个“看起来没生效”的诡异结果。因为标准库需要修改传入对象本身,必须通过指针才能完成赋值。
另一个容易被忽略的问题是:json.Marshal对[]byte会自动做base64编码。因为JSON字符串里不能直
