DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →

Go 1.27 JSON 循环引用报错后,读取 UnsupportedValueError.Value 又触发 panic

JSON 循环引用已经报错,读取 UnsupportedValueError.Value 却再次 panic。先用 errors.As 与 IsValid 保护诊断,再按响应契约切断循环,区分主线修正与正式维护版本。

json.Marshal 已经返回 encountered a cycle,错误处理代码随后调用 UnsupportedValueError.Value.Type(),反而再抛一次反射 panic。在 Go 1.27 的受影响兼容路径中,循环引用错误的 Value 可能是无效的零值;先检查它,才能保住原始的序列化错误。

下面只用两个赋值就形成了循环:

type Node struct {
    Next *Node
}

n := &Node{}
n.Next = n

标准库 JSON 不支持这种对象图。即使修复了错误对象中的 Value,也仍应拒绝循环序列化;不能以“库有回归”为理由忽略 Marshal 的错误并发送空内容。

区分输入循环与诊断字段缺失

在受影响的 Go 1.27.1 上,对这类值调用 encoding/json.Marshal 后,UnsupportedValueError.Str 能保留循环信息,但 Value.IsValid() 返回 false。Go 1.26 对应路径会提供有效的反射值。

这是兼容错误信息的回归,与 JSON 数据本身不合法同时存在。Go 1.27 的旧 encoding/json API 默认由新的实现支持;代码没有显式 import encoding/json/v2,也可能经过这条兼容路径。

先记录实际构建工具链和 import,再找错误处理链中访问 .Value.Type()、.Value.Interface() 的位置。日志里若只剩 reflect: call of reflect.Value... on zero Value,原始的 cycle 错误可能已经被二次异常盖住。

不要只修 NaN 或无穷大的分支就认定此处恢复:它们的 Value 填充已有单独修正,循环检测需要另行处理。

错误处理先判类型,再判 Value 是否有效

下面是可独立理解的函数示例。调用方仍需决定如何把序列化失败返回给业务:

package example

import (
    "encoding/json"
    "errors"
    "fmt"
)

func describeJSONError(err error) string {
    var unsupported *json.UnsupportedValueError
    if !errors.As(err, &unsupported) || unsupported == nil {
        return fmt.Sprint(err)
    }
    if !unsupported.Value.IsValid() {
        return unsupported.Error()
    }
    return fmt.Sprintf("%s (type=%s)",
        unsupported.Error(), unsupported.Value.Type())
}

这里不直接断言 err.(*json.UnsupportedValueError),因为返回的错误也可能来自其他编码失败或被上层包装。IsValid 检查避免为读取补充诊断再次 panic;保留原错误文字即可,不需要把整个循环对象打印进日志。

也不要递归遍历这个对象来“补出”Value,或再次调用 JSON 把它写进错误日志,否则可能重新进入相同的失败路径。

输出模型需要切断回指

接下来检查真正的返回数据。父对象指向子对象、子对象又指回父对象时,可以按接口契约把回指替换成标量 ID,或使用不包含回指的响应 DTO。对于仅作内存导航、不应输出的字段,也可以在明确接口需求后使用 json:"-"。

这些选择会改变输出结构,应同时核对客户端依赖。不能把所有关联字段统一忽略,直到 Marshal 不报错为止;这样可能丢掉接口必需的数据。

恢复标准分两层:错误路径不再二次 panic,正常输出模型也没有循环且包含契约要求的字段。故意构造的循环输入仍应稳定返回错误。仅仅让日志打印成功,不代表业务响应已经能够编码。

截至 2026 年 10 月 20 日,循环错误中填充 Value 的修正已进入主线,1.27 回移事项仍开放。采用维护版本时需要核对真实包含情况,不把主线 issue 关闭当成当前生产工具链已修复。IsValid 防护本身仍适合作为稳健的错误处理保留。