Go 的 json 包仅负责编解码,网络请求需 net/http 等完成;应使用 json.NewDecoder 流式解码、检查 StatusCode、关闭 resp.Body;结构体字段须导出并加 json tag;json.RawMessage 用于延迟解析动态字段;POST 时需设 Content-Type 并处理错误响应;time.Time 和 nil 切片的 JSON 行为需特别注意。

Go 的 encoding/json 包本身不处理网络,它只负责 JSON 编解码;网络请求必须由 net/http 或第三方 HTTP 客户端(如 resty)完成,再把响应体交给 json.Unmarshal 或 json.NewDecoder 处理。
用 http.Get 获取 JSON 并解码到结构体
这是最常见场景:调用 REST API,拿到 JSON 响应后映射到 Go 结构体。关键点是别直接读 resp.Body 字符串再传给 json.Unmarshal,而应使用 json.NewDecoder 流式解码,避免中间字符串拷贝和内存浪费。
常见错误包括:忽略 resp.Body.Close() 导致连接泄漏、没检查 resp.StatusCode 就直接解码(比如 404/500 返回的错误 JSON 会静默失败)、结构体字段未导出(首字母小写)导致解码为零值。
- 结构体字段必须首字母大写(导出),且建议加
jsontag 显式指定键名,尤其当 JSON 键含下划线或大小写不匹配时 - 始终检查
resp.StatusCode,HTTP 状态码非 2xx 时不应继续解码 - 务必调用
defer resp.Body.Close(),否则底层 TCP 连接无法复用 - 用
json.NewDecoder(resp.Body).Decode(&v)比ioutil.ReadAll+json.Unmarshal更省内存
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
resp, err := http.Get("https://api.example.com/user/123")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("API returned %d", resp.StatusCode)
}
var user User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", user)
json.RawMessage 延迟解析嵌套动态字段
当 JSON 中某字段内容不确定(比如可能是对象、数组或字符串),或想跳过校验只暂存原始字节,用 json.RawMessage 最合适。它本质是 []byte,不会触发即时解码,适合做“占位”或二次分发解析。
立即学习“go语言免费学习笔记(深入)”;
典型场景:Webhook 接口收到多种事件类型,type 字段决定后续如何解析 data;或配置项中 options 字段结构多变,先存着等业务逻辑判断后再解。
从一个Perl爱好者到一个Perl程序员。《Intermediate Perl》将教您如何把Perl作为编程语言来使用,而不仅只是作为一种脚本语言。 Perl是一种灵活多变、功能强大的编程语言,可以应用在从系统管理到网络编程再到数据库操作等很多方面。人们常说Perl让容易的事情变简单、让困难的事情变得可行。《Intermediate Perl》正是关于如何将技能从处理简单任务跃升到胜任困难任务的书籍。 本书提供对Perl中级编程优雅而仔细的介绍。由畅销的《学习Perl》作者所著,本书提供了《学习P
-
json.RawMessage字段必须是导出的,且不能是普通string或interface{} - 赋值前需确保其底层字节是合法 JSON(否则后续
json.Unmarshal会报错) - 不能直接打印或比较
json.RawMessage,需先转成string或再次解码
type WebhookEvent struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
// 解析后根据 Type 决定如何处理 Data
var event WebhookEvent
json.Unmarshal(payload, &event)
switch event.Type {
case "user_created":
var u User
json.Unmarshal(event.Data, &u)
case "order_updated":
var o Order
json.Unmarshal(event.Data, &o)
}
POST JSON 请求并处理响应错误
发送 JSON 到服务端时,重点在设置正确 header(Content-Type: application/json)、序列化请求体、以及**统一处理非 2xx 响应体中的错误信息**。很多 API 在 4xx/5xx 时仍返回 JSON 格式的错误详情(如 {"error": "invalid_token"}),直接丢弃会丢失关键调试线索。
容易踩的坑:忘记设 Content-Type 导致服务端拒收;用 bytes.NewReader 包装 json.Marshal 结果但没检查 marshal 错误;对错误响应体不做读取,导致日志里只有状态码没有上下文。
- 始终用
json.Marshal序列化请求体,并检查其返回的 error - 显式设置
req.Header.Set("Content-Type", "application/json") - 对非成功响应,仍要读取
resp.Body 并尝试解码成错误结构体,便于日志和重试判断
type LoginReq struct {
Username string `json:"username"`
Password string `json:"password"`
}
data, _ := json.Marshal(LoginReq{"alice", "pass123"})
req, _ := http.NewRequest("POST", "https://api.example.com/login", bytes.NewReader(data))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
if resp.StatusCode >= 400 {
var errResp struct{ Error string `json:"error"` }
json.NewDecoder(resp.Body).Decode(&errResp)
log.Printf("Login failed: %s (status %d)", errResp.Error, resp.StatusCode)
return
}
var tokenResp struct{ Token string `json:"token"` }
json.NewDecoder(resp.Body).Decode(&tokenResp)
注意 time.Time 和 nil 切片的 JSON 行为
Go 默认把 time.Time 编码为 RFC3339 字符串(如 "2024-05-20T14:30:00Z"),但某些旧系统可能期望 Unix 时间戳整数;而切片字段若为 nil,默认编码为 null,但有些前端库会把 null 当成缺失字段而非空数组,导致 JS 侧逻辑异常。
这两个问题不报错,但行为不符合预期,调试时很难定位——因为 JSON 看起来“格式正确”,只是语义不对。
- 自定义时间类型并实现
MarshalJSON/UnmarshalJSON方法可切换为时间戳 - 用指针切片(
*[]string)或自定义类型配合MarshalJSON可让nil切片输出[]而非null - 第三方包如
github.com/mitchellh/mapstructure在从map[string]interface{}转结构体时,对时间字符串解析更宽松,但会损失类型安全
真正麻烦的是:这些行为差异往往只在联调特定服务时暴露,本地单元测试容易漏掉。









