Skip to content

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 对比

概念GoJavaScript / TypeScript
序列化json.MarshalJSON.stringify
反序列化json.UnmarshalJSON.parse
字段映射struct tag属性名即 JSON key
私有字段小写不可导出#private / 无此概念
类型静态,需定义 struct动态 object
数字区分 int/float64全是 Number
undefined无,用 omitempty 或指针undefined 被 JSON 忽略

关键差异

  1. Go 必须预先定义 struct,不能像 JS 随意增删属性(除非用 map[string]any
  2. JSON 数字默认解析为 float64(Unmarshal 到 any 时)
  3. 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 很大时用 stringjson.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. 学习检查

  1. Unmarshal 为什么第二个参数必须是指针?
  2. struct tag 中 omitempty- 分别做什么?
  3. 未导出的字段能否参与 JSON 序列化?
  4. JSON 数字 Unmarshal 到 any 时是什么类型?
  5. MarshalEncoder.Encode 的使用场景有何不同?
  6. 如何设计 JSON 字段名以匹配 Vue 前端的 camelCase 习惯?

12. 下一步

已完成下一知识点关系
JSONpackage design将 model、handler、store 分到不同包
HTTP / REST APIJSON 是 API 请求响应的核心格式

建议学习 package design 组织代码结构,然后进入 Phase 03 Web 开发。


学习导航

上一篇:Go Error Handling · 对应练习 · 下一篇:Go Package Design