跳到主要内容

JSON 转 Go 结构体:别再手敲 struct 和 json 标签

把一段 JSON 粘进来就拿到带 json 标签、字段已导出、嵌套子结构体和切片类型都推断好的 Go struct。讲清类型推断、可空指针、对象数组合并的原理与坑。

发布于 作者 李雷
#Go #JSON #结构体 #类型推断 #后端

JSON 转 Go 结构体:别再手敲 struct 和 json 标签

接第三方接口时,最磨人的不是写业务逻辑,是对着一段 30 多字段的 JSON 响应,一个字段一个字段地敲 Go 结构体。字段名要从 created_at 改成 CreatedAt,首字母得大写否则 encoding/json 根本读不到它,后面还要补一长串 json:"created_at" 标签,嵌套对象再单独拆一个子 struct。三十个字段敲完,半小时没了,还容易把 html_url 写成 HtmlUrl 而不是 Go 圈约定的 HTMLURL

把这件事交给工具做更划算。粘 JSON,出 struct,标签、大小写、嵌套、切片类型全自动推断好,直接拖进 .go 文件就能用。

为什么字段必须首字母大写

这是新手最容易栽的坑。在 Go 里,一个标识符只有首字母大写时才是导出的,包外可见,encoding/json 才够得着它。一个叫 id 的字段是非导出的,json.Unmarshal 会悄悄跳过它,结构体回来是空的,你还以为是接口返回有问题,白白排查一小时。

所以生成出来的字段一律是 IDNameCreatedAt。原始的小写 JSON key 不会丢,它被保存在 json:"id" 标签里,线上传输的格式完全不变,只有 Go 这边的标识符被大写了。约定俗成的缩写 IDURLHTTPAPI 会整段保持大写,这正是 golint 和 staticcheck 想要的样子。

类型推断:int、float64 和那些坑

JSON 只有一种数字类型,工具只能靠样本猜。整数样本推断成 int,带小数点的推断成 float64。如果同一个字段在不同样本里有时是整数有时带小数,它会放宽到 float64,免得你 unmarshal 时炸掉,因为 Go 在反序列化时不做 int 和 float 之间的隐式转换。

需要 int64 的场景也很明确:毫秒级 Unix 时间戳、Twitter 或 Discord 的雪花 ID、字节数,这些在 32 位构建上可能超过 2^31-1,一个开关切过去就行。

提醒一句别想当然:很多 API 的 ID 其实是字符串(Stripe 的 cus_123、各类 UUID 接口),它们会被推成 string 而不是数字,这是对的,别手动改成 int64,改了反而让 unmarshal 崩。

嵌套对象、切片与对象数组合并

嵌套对象会自动抽成子 struct。比如 GitHub user 里那个 plan 对象,根名设成 GitHubUser 之后,它会被抽成 GitHubUserPlan,字段类型干净地引用过去。

真正值得说的是对象数组的处理。喂它这一段:

[
  {"name": "a"},
  {"name": "b", "fork": true}
]

出来的不是两个几乎重复的 struct,而是合并成一个:

type Root struct {
	Name string `json:"name"`
	Fork bool   `json:"fork,omitempty"`
}

Fork 因为第一个元素里没有,被判定为可选。合并后的这个 struct 还会作为切片的元素类型,所以字段写出来是 []Root,而不是没法用的 []interface{}。一个粗糙的生成器会出两个结构体让你手动对齐,这里省掉了这步对齐的活。

可空字段:指针还是零值

两种信号会让一个字段变可空:它在 JSON 里出现过 null,或者它在对象数组的部分元素里有、部分元素里没有。

关闭"指针"开关时,字段保留值类型,你靠 Go 的零值来表示"缺失"。打开"指针",字段变成 *string*int,你就能在 handler 里用 if e.CancelledAt != nil 干净地区分"发了个 0"和"压根没发"。处理那种有时带 cancelled_at、有时不带的 webhook,这个区分很关键。

至于 ,omitempty,一个开关给所有标签加上,它让 marshal 时把零值字段从输出里删掉,适合生成紧凑的 PATCH body。但别无脑全加:对那些 0false 本身有意义的字段,一个你真想发的 "count": 0 会凭空消失。

我自己的用法

我接一个新接口的固定动作是:curl 一次响应,粘进 JSON 转 Go 结构体工具,根名按业务命名,复制进 types.go。原来那种 var data map[string]interface{} 然后到处 .(float64) 类型断言的写法,被一个真正类型化的 struct 替掉,编译期就能查出拼错的 key,而不是上线后在运行时才挂。一来一回省下的时间,比工具本身好用更让我离不开它。

如果粘进去报解析错,先别怀疑工具:它用的是严格的 JSON.parse,注释、末尾逗号、不带引号的 key 都会报错。这时先过一道 JSON 格式化工具把 JSON 理顺再来。整个过程全在浏览器里跑,JSON 不碰服务器。


Made by Toolora · Updated 2026-06-13