Go 的 json.Unmarshal 不支持运行时类型选择,因其实现为纯机械字段映射,不解析内容逻辑;解决多态需两步法:先用 KindHeader 提取 kind,再用 json.RawMessage 分发至具体 struct。

Go 的 json.Unmarshal 本身不支持运行时类型选择,遇到带 kind 字段的多态 JSON(比如 {"kind":"circle","radius":1}),直接用一个 struct 去解,字段必然丢失或零值——这不是 bug,是设计使然。
为什么标准 json.Unmarshal 无法自动识别多态结构
Go 的 encoding/json 在反序列化时只认字段名和类型,不看内容逻辑。它不会读取 kind 字段再去决定该用 Circle 还是 Rectangle 结构体去填值。你给它一个统一 struct,它就按那个 struct 的字段布局硬塞;你给它 interface{},它就停在 map 或 []interface{} 层,不再往下走。
-
json.Unmarshal不会执行任何分支判断,也不调用用户逻辑,纯机械映射 - 即使字段名一致,
Circle.radius和Rectangle.width是两个不同字段,无法共存于同一 struct 的非指针字段中 - 试图用嵌入 +
omitempty拼凑“联合体”,会导致字段命名污染、零值覆盖、类型安全归零
最简可行:先读 kind,再分发到具体 struct
绕过反射的复杂性,用两步法解决 90% 场景:先用轻量结构提取 kind,再根据值选目标 struct,最后重新 Unmarshal。性能损失极小,逻辑清晰,无 unsafe 风险。
- 定义一个仅含
Kind字段的哨兵 struct:type KindHeader struct { Kind string `json:"kind"` } - 用
json.RawMessage暂存剩余字节:var raw json.RawMessage; err := json.Unmarshal(data, &raw) - 先解析
KindHeader获取类型标识,再用json.Unmarshal(raw, &target)精准填充 - 避免重复解析整个 payload,
json.RawMessage是字节切片引用,零拷贝
需要泛化?用自定义 UnmarshalJSON + 反射做类型路由
当多态层级深、结构多、或需嵌套在数组/对象中时,必须让某个顶层 struct 承担路由职责。这时要实现 UnmarshalJSON 方法,内部手动 dispatch,反射只用于字段赋值,不用于类型决策。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 不要在
UnmarshalJSON里用反射遍历所有可能 struct 并试错——太慢且不可维护 - 把
kind → struct 类型映射写死在 map 中:map[string]reflect.Type{"circle": reflect.TypeOf(Circle{})} - 用
reflect.New(t).Interface()创建实例,再用json.Unmarshal填充,最后赋给目标字段 - 注意:接收者必须是指针,否则
UnmarshalJSON修改不了字段值
别踩这些坑
反射不是银弹,尤其在 JSON 多态场景下,容易把简单问题搞复杂:
- 别用反射代替
type switch做接口断言——接口已知类型时,switch v.(type)更快更安全 - 别在
UnmarshalJSON里调reflect.Value.Set()直接塞原始 map —— 字段名大小写、tag、omitempty 全失效 - 别依赖
unsafe.Pointer投影多个 struct —— 内存布局兼容性脆弱,Go 版本升级可能破坏 - 如果只是 API 客户端,优先考虑生成代码(如
go-swagger或oapi-codegen),比手写反射稳得多
真正难的不是怎么 dispatch,而是怎么让错误路径有明确提示:kind="triangle" 却没注册对应 struct?这时候 panic 不如返回 fmt.Errorf("unknown kind: %s", kind) —— 生产环境里,可读的错误比“优雅”的反射更重要。

















