json.marshal 返回空字符串或错误的根本原因是结构体字段未导出(首字母小写)或值不可序列化(如func、chan、循环引用)。

json.Marshal 为什么返回空字符串或错误?
根本原因通常是结构体字段没导出(首字母小写),或者值本身不可序列化(比如 func、chan、含循环引用的指针)。
Go 的 json.Marshal 只能处理导出字段(即首字母大写的字段)。如果字段是 name string,它直接被忽略,不会报错,但也不会出现在结果里——看起来像“生成了空 JSON”。
- 检查结构体字段是否全部首字母大写;需要忽略的字段加
json:"-"标签 - 嵌套结构体也要逐层检查导出性,别只看顶层
-
time.Time能序列化,但默认输出 RFC3339 字符串;如需自定义格式,得用MarshalJSON方法 - 含
map[interface{}]interface{}的值会 panic;必须用map[string]interface{}
怎么控制字段名、省略空值、处理时间格式?
靠结构体 tag。它不是装饰,而是 json.Marshal 解析字段行为的唯一配置入口。
常见 tag 形式:json:"user_id,omitempty" 表示序列化时用 user_id 作 key,且值为零值(""、0、nil 等)时整个字段不出现。
-
omitempty对指针、切片、map 有效,但对非空字符串"0"或数字0也会删掉——注意业务上0是否有含义 - 时间字段推荐显式指定格式:加方法实现
MarshalJSON(),而不是依赖默认 RFC3339;否则前端可能解析失败 - 字段名含下划线或大小写混用,直接写在 tag 里,比如
json:"order_status",别指望自动转蛇形 - tag 中不能有空格:
json:"name,omitempty"末尾空格会导致 tag 解析失败,字段被跳过
json.Marshal 和 json.MarshalIndent 性能差多少?
差一个数量级。带缩进的版本要多做三次遍历:一次序列化原始字节,两次重排加空白符。日常调试可以,线上 API 响应别用。
如果你看到接口响应慢且用了 json.MarshalIndent,换回 json.Marshal 通常能立竿见影。
-
json.MarshalIndent主要用于日志打印、配置导出、CLI 工具输出等非性能敏感场景 - 想“又好看又快”,得自己缓存格式化结果,或用第三方库(如
github.com/tidwall/pretty),但多数服务不需要 - HTTP 响应头设了
Content-Type: application/json后,JSON 是紧凑还是换行,前端完全无感
为什么 struct{} 或 nil slice 序列化成 null?
因为 Go 的零值语义和 JSON 的 null 映射是明确的:nil slice、nil map、nil pointer 都变成 null;空结构体 struct{} 变成 {};但 var s struct{} 不是 nil,它只是无字段。
容易误判的是 slice:声明但未 make 的 slice 是 nil,make 但 len=0 的 slice 是 []。两者 JSON 表现完全不同。
- API 返回中,
null和[]对前端意味着不同逻辑(是否有数据容器),别混用 - 想强制让空 slice 输出
[],就别让它为 nil:初始化用items := make([]string, 0),而不是var items []string - struct{} 没字段,所以
json.Marshal(struct{}{})得到{},不是null;但它没法承载任何信息,慎用作占位
最常被忽略的其实是字段 tag 的拼写细节:少个引号、多空格、用中文引号、写成 json:"name, omitempty"(逗号后多了空格)——这些都会让 tag 失效,字段悄悄消失。调试时先 fmt.Printf("%#v", yourStruct) 看实际传进去的是什么,比猜快得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











