Go语言通过标准库encoding/json的json.Marshal和json.Unmarshal实现JSON序列化与反序列化,要求结构体字段导出(首字母大写)、正确使用json标签(如json:"name"或json:"name,omitempty"),反序列化必须传指针,omitempty按类型零值触发。

json.Marshal 和 json.Unmarshal 不是框架功能,而是 Go 标准库 encoding/json 提供的底层能力。所有 Golang Web 框架(如 Gin、Echo、Chi)都基于它做封装,但核心逻辑和限制完全一致——没绕过导出规则、没自动修复字段可见性、也没隐藏 omitempty 的行为差异。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
结构体字段必须首字母大写才能被序列化
Go 的反射机制只允许外部包访问导出(exported)标识符,encoding/json 也不例外。小写开头的字段(如 name、created_at)在 json.Marshal 后永远输出空对象 {},哪怕写了 json:"name" 标签也无效。
- 字段名必须大写:用
Name替代name,CreatedAt替代created_at - 标签控制 JSON 键名:
json:"name"可以让Name字段序列化为小写键 -
time.Time字段默认序列化为 RFC3339 字符串(如"2026-06-17T01:31:00Z"),无需额外处理
切片、map、指针的反序列化要传地址
json.Unmarshal 必须接收目标值的指针,否则修改不会生效。常见错误是传值而非取址,尤其在嵌套结构或切片初始化时。
- 对结构体变量:用
&user,不是user - 对切片变量:用
&users,不是users(即使users是[]User类型) - 对 map:同样需要指针,
var m map[string]interface{}→json.Unmarshal(data, &m) - 如果目标变量未初始化(如 nil map 或 nil slice),
Unmarshal会自动分配内存,但前提是传了指针
omitempty 不是“忽略空字符串”,而是忽略零值
omitempty 的触发条件取决于类型的零值:字符串为 "",整数为 0,布尔为 false,指针/slice/map/interface{} 为 nil。它不区分“业务上为空”和“技术上为零”。
-
Email string <code>json:"email,omitempty":当Email == ""时不输出该字段 -
Age int <code>json:"age,omitempty":当Age == 0时也不输出——这常导致年龄为 0 的用户丢失字段 - 若需保留零值但跳过空字符串,应拆分为两个字段,或改用指针类型(
*string),此时nil才触发 omitempty - 注意:嵌套结构体字段若整体为零值(如
Address{}),且该字段带omitempty,整个字段会被省略
第三方 set 类型(如 golang-set)需显式调用 MarshalJSON
标准库不识别自定义集合类型,json.Marshal 直接调用会失败或返回空数组。golang-set/v2 提供了 MarshalJSON() 和 UnmarshalJSON() 方法,必须显式使用。
- 正确方式:
data, err := mySet.MarshalJSON(),或err := mySet.UnmarshalJSON(b) - 不能直接传 set 给
json.Marshal:会得到[]或 panic(取决于实现) - 线程安全与非线程安全版本接口一致,但类型不同,不可混用
- 如果你在框架中间件里统一处理响应,记得对 set 类型做类型断言或提前转换为 slice
字段导出规则是硬约束,不是风格偏好;omitempty 的零值判断是按类型定义的,不是按业务语义;而自定义类型永远需要自己提供序列化逻辑——这些点一旦漏掉,调试时往往卡在“为什么字段不见了”这种低级但耗时的问题上。

















