外观
Go JSON
Phase 02 — Go Core 涵盖:
JSON
1. 学习目标
完成本知识点后,你应该能够:
- 使用
encoding/json进行 Marshal / Unmarshal - 理解 struct tag(
json:"fieldName")的作用 - 处理 omitempty、
-、嵌套 struct 和匿名 struct - 解析动态 JSON(
map[string]any、json.RawMessage) - 在 HTTP API 中正确读写 JSON 请求体和响应体
- 为数字孪生设备数据设计 JSON 结构
2. 为什么需要
前后端协作的核心数据格式就是 JSON。Vue 前端和 Three.js 场景消费的数据,Go 后端必须能稳定地序列化和反序列化。Go 的 JSON 处理依赖 struct tag 映射字段名,与 JS 的对象字面量看似相同,但类型、零值、指针字段的行为差异很大。
Phase 03 构建 REST API 时,几乎每个 handler 都要处理 JSON——现在打好基础,后面写 API 会顺畅很多。
3. 核心概念
3.1 Marshal 与 Unmarshal
| 函数 | 方向 | 签名 |
|---|---|---|
json.Marshal(v) | Go → []byte | 序列化 |
json.Unmarshal(data, &v) | []byte → Go | 反序列化 |
Unmarshal 的第二个参数必须是指针。
3.2 struct tag
go
type Device struct {
ID string `json:"id"`
Speed float64 `json:"speed"`
Battery int `json:"battery,omitempty"`
Internal string `json:"-"`
}json:"name":JSON 字段名omitempty:零值时省略-:不参与序列化
3.3 导出字段
只有首字母大写的字段才能被 JSON 包访问。小写字段会被忽略。
3.4 指针字段
指针为 nil 时,Unmarshal 会创建新值;omitempty 时 nil 指针会省略。
4. 基础语法
完整示例见 workspace/phase-02/json/main.go。
go
package main
import (
"encoding/json"
"fmt"
"log"
)
type AGVState struct {
ID string `json:"id"`
X float64 `json:"x"`
Y float64 `json:"y"`
Speed float64 `json:"speed,omitempty"`
Battery int `json:"battery"`
Online bool `json:"online"`
}
func main() {
// 序列化
agv := AGVState{
ID: "AGV-001", X: 10.5, Y: 3.2,
Battery: 85, Online: true,
}
data, err := json.Marshal(agv)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
// {"id":"AGV-001","x":10.5,"y":3.2,"battery":85,"online":true}
// 反序列化
jsonStr := `{"id":"AGV-002","x":0,"y":0,"battery":50,"online":false}`
var parsed AGVState
if err := json.Unmarshal([]byte(jsonStr), &parsed); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", parsed)
// 格式化输出
pretty, _ := json.MarshalIndent(agv, "", " ")
fmt.Println(string(pretty))
// 动态 JSON
var raw map[string]any
_ = json.Unmarshal([]byte(jsonStr), &raw)
fmt.Println("id from map:", raw["id"])
}5. 代码解析
go
type AGVState struct {
Speed float64 `json:"speed,omitempty"`
}omitempty:Speed 为 0 时不出现在 JSON 中。注意:0、false、"" 都是零值,可能意外省略有效数据。
go
json.Unmarshal([]byte(jsonStr), &parsed)必须传指针 &parsed,否则无法修改 parsed 的值。
go
var raw map[string]any
json.Unmarshal(data, &raw)字段不确定时用 map 或 json.RawMessage 延迟解析。
go
json.MarshalIndent(v, "", " ")调试和 API 响应格式化常用,生产环境通常 Marshal 即可(更紧凑)。
6. JavaScript / TypeScript 对比
| 概念 | Go | JavaScript / TypeScript |
|---|---|---|
| 序列化 | json.Marshal | JSON.stringify |
| 反序列化 | json.Unmarshal | JSON.parse |
| 字段映射 | struct tag | 属性名即 JSON key |
| 私有字段 | 小写不可导出 | #private / 无此概念 |
| 类型 | 静态,需定义 struct | 动态 object |
| 数字 | 区分 int/float64 | 全是 Number |
| undefined | 无,用 omitempty 或指针 | undefined 被 JSON 忽略 |
关键差异:
- Go 必须预先定义 struct,不能像 JS 随意增删属性(除非用
map[string]any) - JSON 数字默认解析为
float64(Unmarshal 到any时) - Go 没有
undefined;缺失字段保持零值
7. 常见错误
错误 1:Unmarshal 忘记传指针
go
var agv AGVState
json.Unmarshal(data, agv) // ❌ 编译错误
json.Unmarshal(data, &agv) // ✅错误 2:字段未导出
go
type Device struct {
id string `json:"id"` // ❌ 小写,JSON 包无法访问
}错误 3:omitempty 误用
go
Battery int `json:"battery,omitempty"` // 电量 0 会被省略,前端可能误解
// 电量 0 有意义时用指针 *int 或去掉 omitempty错误 4:时间格式
go
// time.Time 默认 RFC3339:"2006-01-02T15:04:05Z07:00"
// 自定义格式需实现 json.Marshaler 接口错误 5:大数字精度
JSON 数字到 float64 可能丢精度;ID 很大时用 string 或 json.Number。
8. 实际应用
数字孪生 — WebSocket 推送格式
go
type TwinUpdate struct {
Type string `json:"type"` // "position" | "status"
DeviceID string `json:"deviceId"`
Timestamp int64 `json:"timestamp"`
Payload AGVState `json:"payload"`
}与 Three.js 前端约定字段名(camelCase tag),保证序列化一致。
REST API 响应
go
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}统一响应结构,Vue axios 拦截器可统一处理。
9. 深入理解
9.1 json.Marshaler / json.Unmarshaler
自定义类型的序列化行为:
go
func (t Time) MarshalJSON() ([]byte, error) { ... }9.2 Encoder / Decoder(流式)
大量数据或 HTTP Body 时用 json.NewDecoder(r).Decode(&v),不必一次读入全部 bytes。
9.3 性能提示
- 生产环境复用 buffer(
sync.Pool)或考虑jsoniter等库——初学阶段标准库足够 - 避免在热路径频繁 Marshal/Unmarshal 大 struct
9.4 与 Phase 03 的衔接
HTTP handler 中典型模式:
go
json.NewDecoder(r.Body).Decode(&req)
json.NewEncoder(w).Encode(resp)10. 练习
详细题目见
exercises/phase-02-core/03-json.md。
Level 1 — 基础
练习 1.1:定义 AGVState struct 和 json tag,Marshal 并打印。
练习 1.2:从 JSON 字符串 Unmarshal 到 struct,打印各字段。
练习 1.3:验证小写字段不会被序列化。
Level 2 — 应用
练习 2.1:使用 omitempty,对比零值和非零值的 JSON 输出。
练习 2.2:Unmarshal 到 map[string]any,读取嵌套字段。
练习 2.3:设计 SensorReading struct(timestamp、value、unit)。
Level 3 — 综合
练习 3.1:实现批量设备状态 JSON 数组的序列化/反序列化。
练习 3.2:用 json.RawMessage 处理 type 字段不同的多态消息。
Level 4 — 项目实践
练习 4.1:在 workspace/phase-02/json/ 构建 twin message 模块:定义消息类型、Marshal/Unmarshal、模拟 HTTP Body 读写。
11. 学习检查
- Unmarshal 为什么第二个参数必须是指针?
- struct tag 中
omitempty和-分别做什么? - 未导出的字段能否参与 JSON 序列化?
- JSON 数字 Unmarshal 到
any时是什么类型? Marshal和Encoder.Encode的使用场景有何不同?- 如何设计 JSON 字段名以匹配 Vue 前端的 camelCase 习惯?
12. 下一步
| 已完成 | 下一知识点 | 关系 |
|---|---|---|
| JSON | package design | 将 model、handler、store 分到不同包 |
| HTTP / REST API | JSON 是 API 请求响应的核心格式 |
建议学习 package design 组织代码结构,然后进入 Phase 03 Web 开发。