讲解

JSON 是 Web 接口的事实标准数据格式,Go 标准库的 encoding/json 包提供了完整的编解码能力。序列化(Go 值 → JSON 文本)用 json.Marshal,反序列化(JSON 文本 → Go 值)用 json.Unmarshal,两个函数配合结构体就能覆盖绝大多数场景:Marshal 接收任何值返回 []byte,Unmarshal 接收 JSON 字节和目标变量的指针。

结构体字段与 JSON 键的对应规则:只有导出字段(大写开头)参与编解码;默认用字段名做键,实际项目里 JSON 习惯小写键,于是用「结构体标签」指定——字段后面跟反引号包起来的 tag:Name string json:"name"。标签里还能加选项:json:"name,omitempty" 表示零值时省略该字段,json:"-" 表示完全忽略。

JSON 结构不确定或只是临时取一两个字段时,可以反序列化到 map[string]any:所有 JSON 值被解析成对应的 Go 类型(对象→map、数组→[]any、数字→float64、字符串→string),取用时再类型断言。方便但放弃了类型安全,结构稳定的接口还是应该定义结构体——编译器会帮你发现字段写错。

示例

结构体序列化为 JSON:

package main

import (
	"encoding/json"
	"fmt"
)

type Book struct {
	Title  string  `json:"title"`
	Author string  `json:"author"`
	Price  float64 `json:"price,omitempty"`
	stock  int     // 小写字段不参与序列化
}

func main() {
	b := Book{Title: "Go 入门", Author: "张三", Price: 59.9, stock: 100}
	data, err := json.Marshal(b)
	if err != nil {
		fmt.Println("序列化失败:", err)
		return
	}
	fmt.Println(string(data))

	pretty, _ := json.MarshalIndent(b, "", "  ")
	fmt.Println(string(pretty))
}

JSON 反序列化到结构体,未知字段被忽略:

package main

import (
	"encoding/json"
	"fmt"
)

type Book struct {
	Title string  `json:"title"`
	Price float64 `json:"price"`
}

func main() {
	raw := `{"title":"Go 入门","price":59.9,"extra":"多余字段被忽略"}`
	var b Book
	if err := json.Unmarshal([]byte(raw), &b); err != nil {
		fmt.Println("解析失败:", err)
		return
	}
	fmt.Printf("%+v\n", b)
}

结构未知时用 map 接收:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	raw := `{"name":"小明","age":20,"tags":["go","json"]}`
	var m map[string]any
	if err := json.Unmarshal([]byte(raw), &m); err != nil {
		fmt.Println("解析失败:", err)
		return
	}
	fmt.Println("name:", m["name"])
	fmt.Println("age(数字一律解析为 float64):", m["age"])
	fmt.Println("tags:", m["tags"])
}

常见坑

  • Unmarshal 忘记传指针:json.Unmarshal(data, b) 传值不生效(函数改不到调用方的变量),必须传 &b。好在这种错误会在运行时直接报 InvalidUnmarshalError,不难排查。
  • 期待小写字段被序列化:只有导出字段参与 JSON 编解码,title string 小写字段会被静默跳过,序列化结果里「丢字段」先检查大小写。
  • 数字精度的暗坑:JSON 数字默认解析为 float64,超大整数(如 18 位 ID)会丢精度。对精度敏感的 ID 用 json.Number 或 string 类型承接。
  • time.Time 的格式错配:JSON 里的 "2025-01-01" 不是 time.Time 默认认的 RFC3339 格式,Unmarshal 会报错。要么用 RFC3339 格式传输,要么自定义 UnmarshalJSON。
  • 在热路径反复 Marshal 大对象:encoding/json 基于反射,性能一般。极致性能场景可考虑预生成代码的第三方库(如 easyjson),但先把 profiling 做了再优化。

小结

Marshal/Unmarshal 配合结构体标签处理 JSON;导出字段才参与,omitempty 省略零值,map[string]any 应对未知结构。下一节用 JSON 知识搭一个真正的 HTTP 服务器。