JSON 转 Go Struct
把 JSON 生成带 json tag 的 Go struct,嵌套对象拆成独立结构体,字段名自动转大驼峰。
手写 Go struct 最烦的是 tag:字段名要转成大驼峰,json tag 里的键名又必须和原始 JSON 完全一致,下划线一多就容易抄错,反序列化时对应字段静默为零值,排查起来还得回头翻接口文档。把样例 JSON 粘进来,struct 定义和 json tag 会一起生成,字段顺序与 JSON 一致,便于逐行核对。
生成逻辑全部运行在浏览器里,接口样例不会发送到任何服务器。它只根据这一份 JSON 推断类型,整数与小数的区分、null 与空数组的处理都遵循固定规则,推断不出来的部分退回 interface{} 而不是猜一个类型,所以结果可以直接编译;但指针、omitempty 这类语义取舍仍要按业务手工补。
类型映射与 json tag
数字按是否为整数分成 int 和 float64,注意 JSON 里写 1.0 也会被当成整数生成 int,因为 JS 的数字不区分整型与浮点。true 和 false 生成 bool,字符串生成 string,null 生成 interface{},空数组生成 []interface{}。每个字段都带 json tag,tag 里写的是原始键名,所以 user_name 这样的下划线键能正确反序列化,字段名本身转成大驼峰 UserName。生成器不加 omitempty,也不使用指针类型。
指针与零值的取舍
由于 null 一律生成 interface{},并且没有指针和 omitempty,生成结果无法区分「字段缺失」「显式传 null」和「零值」这三种情况。做部分更新接口或需要区分语义时,要手工把字段改成 int、string 的指针类型并加 omitempty,或者改用 sql.NullString 这类包装类型。反过来,如果只是读取展示,interface{} 足够用,代价是取值时要写类型断言,漏写断言会在运行时报错。
嵌套结构与命名冲突
嵌套对象生成独立 struct,名字由键名转大驼峰。根节点是数组时只按第一个元素生成一个 struct,不会额外生成切片类型,使用处要自己写切片形式;顶层结构体名固定为 AutoGenerated。命名转换只处理下划线和连字符,含点号或空格的键会生成非法标识符,需要手工改;更棘手的是 user_name 与 user-name 会归一成同一个字段名,同时出现会产生重复字段导致编译失败。大整数在解析阶段就已丢精度,建议该字段直接用 string 或 json.Number。
常见问题
- 生成结果里的 int 和 float64 是怎么判断的?
- 按数值是否带小数位判断,是整数就生成 int,否则 float64。注意 JSON 里写 1.0 会被解析成整数 1,字段仍然是 int。金额、比率这类即使当前值恰好是整数也应手工改成 float64 或用 decimal 库类型,避免后续被截断。
- 为什么 null 生成 interface{} 而不是指针类型?
- 生成器对 null 不做类型猜测,统一退回空接口。这样代码能直接编译,但读值时要写类型断言,也无法区分字段缺失与显式 null。需要严格语义时手工改成指针类型并加上 omitempty,或者换成对应的 Null 包装类型。
- 根节点是数组时为什么只生成了一个 struct?
- 生成器只取数组第一个元素推断结构,输出单个 struct 定义,不生成切片别名,使用处写该结构体的切片即可。如果数组各元素的字段差异较大,第一个元素推断出的字段会偏少,需要对照真实数据补齐,或换一条字段最完整的记录作为输入。
- 改了字段名会不会导致 json tag 失效?
- 不会。tag 写的是 JSON 里的原始键名,字段名怎么转大驼峰都不影响反序列化,这也是生成器一定要输出 tag 的原因。只有手工改了 tag 或键名本身变化时才会绑不上;不想映射的字段可以用短横线忽略,可选字段则可以在 tag 里加 omitempty。