登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go jsonunmarshal 出错时怎么查递归栈

来源:17golang原创

时间:2026-09-13 11:02:56 118浏览 收藏

Go 里自定义 UnmarshalJSON 时,如果方法内部又把原对象传给 json.Unmarshal,最典型的结果不是“JSON 层级太深”,而是同一个解码方法不断重入,最后看到递归栈或栈溢出。处理办法很固定:在方法内部定义一个与原结构体字段相同、但没有方法集的别名类型,把别名指针交给默认解码,再把成功结果写回接收者。

要点速览
  • 原类型实现了 UnmarshalJSONjson.Unmarshal 会优先调用它。
  • 别名类型不携带原类型的方法集,可以切断回调递归。
  • 临时值解码成功后再覆盖接收者,错误要原样返回。

递归栈从哪里开始:UnmarshalJSON 的回调边界

encoding/json 的默认解码会检查目标值是否实现 json.Unmarshaler。下面这种写法看起来想“复用默认解码”,实际却再次把 *Profile 交给 json.Unmarshal

type Profile struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

// UnmarshalJSON 负责自定义 Profile 的 JSON 解码。
func (p *Profile) UnmarshalJSON(data []byte) error {
	// 这里的 *Profile 仍然实现 Unmarshaler,会再次进入本方法。
	return json.Unmarshal(data, p)
}

调用链会变成 json.Unmarshal → Profile.UnmarshalJSON → json.Unmarshal → Profile.UnmarshalJSON。所以看到连续重复的 UnmarshalJSON 栈帧时,先查“方法内部是否把原类型传回 json.Unmarshal”,不要先去调大栈或删掉嵌套字段。

Go jsonunmarshal 递归栈中 json.Unmarshal 与 Profile.UnmarshalJSON 的回调边界关系示意图
图1:Go jsonunmarshal 递归边界操作示意图;重点看原类型回调与默认解码分组之间的重复连接。

别名解码为什么能切断递归

Go 的类型别名写法是 type profileAlias Profile。它沿用字段布局,却不会把 Profile 的方法集一起带过去。于是默认解码看到的是一个普通结构体,不会再次触发自定义方法:

func (p *Profile) UnmarshalJSON(data []byte) error {
	// 别名保留字段和 json tag,但不继承 Profile 的方法集。
	type profileAlias Profile
	var decoded profileAlias

	// 默认解码只处理字段,不会回调 Profile.UnmarshalJSON。
	if err := json.Unmarshal(data, &decoded); err != nil {
		return err // 保留类型错误、语法错误等原始信息。
	}

	// 成功后一次性提交,避免错误输入污染原对象。
	*p = Profile(decoded)
	return nil
}

如果希望复用当前对象已有字段,也可以写成 decoded := profileAlias(*p),但要明确这会保留未出现在 JSON 中的旧值。创建零值临时对象更容易判断“缺失字段应保持什么语义”。

Go jsonunmarshal 使用 profileAlias 切断方法集回调并写回 Profile 的数据结构关系示意图
图2:别名类型切断 Go jsonunmarshal 回调的结果示意图;字段布局进入默认解码,成功数据再回到 Profile。

四个检查点能排除大多数误判

现象优先检查判断
栈帧重复出现同一个方法是否把原类型传回 json.Unmarshal优先修复方法集递归
只报字段类型错误JSON 值与 Go 字段类型是否匹配属于输入或字段映射问题
错误后对象出现半成品是否直接写入接收者改用临时值成功后提交
方法偶发崩溃指针接收者是否为 nil先做接收者边界处理

排查时保留一个最小 JSON,例如 {"name":"Lin","age":20},先确认递归是否消失,再逐个加回嵌套字段和自定义规则。不要把“使用别名类型”误解成复制字段定义;它的关键价值是改变方法集,字段仍由默认解码器按标签匹配。

常见问题

为什么不能直接把 Profile 改成指针别名?

指针层级变化不能自动移除原类型的方法集,关键是让解码目标成为没有 UnmarshalJSON 方法的定义类型。

别名解码会不会丢掉 json 标签?

不会。别名保留底层结构体的字段和标签,默认 encoding/json 仍按标签或字段名匹配。

递归栈一定是 JSON 数据太深吗?

不一定。自定义解码方法重入是更常见的原因;只有确认没有回调递归后,才继续检查输入嵌套深度和数据结构。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>