
Go 的 json 包在编码/解码时会解引用指针,将 *J 转为 J 值本身;因此原结构中 A[0] 和 B[0] 指向同一地址的指针,在 JSON round-trip 后会变成两个独立的、值相等但地址不同的 J 实例——这是符合规范的预期行为。
go 的 `json` 包在编码/解码时会解引用指针,将 `*j` 转为 `j` 值本身;因此原结构中 `a[0]` 和 `b[0]` 指向同一地址的指针,在 json round-trip 后会变成两个独立的、值相等但地址不同的 `j` 实例——这是符合规范的预期行为。
在 Go 中,encoding/json 对指针类型的处理是按值语义而非引用语义进行的。官方文档明确说明:
"Pointer values encode as the value pointed to."
这意味着json.Marshal不会保存指针地址,而是递归地序列化其指向的值;而json.Unmarshal则为每个字段重新分配内存并构造新值,即使原始指针指向同一对象,反序列化后也会生成彼此独立的副本。
以你的结构为例:
type J struct { X, Y int }
type N struct {
A, B, C []*J
}
若初始化时 n.A[0] = &jInst; n.B[0] = &jInst(即共享同一 J 实例),经 json.Marshal → 文件存储 → json.Unmarshal 后:
-
n.A[0]指向一个新分配的J(值等于jInst); -
n.B[0]指向另一个新分配的J(值也等于jInst); - 二者值相等(
*n.A[0] == *n.B[0]为true),但地址不同(n.A[0] == n.B[0]为false)。
✅ 正确的比较方式是比较值而非指针:
fmt.Println("same value:", *n.A[0] == *n.B[0]) // ✅ true after decode
fmt.Println("same pointer:", n.A[0] == n.B[0]) // ❌ false —— 不应依赖此判断
⚠️ 注意事项:
- 若业务逻辑强依赖指针相等性(如通过
==判断对象身份、实现缓存或图结构遍历),则 JSON 不适合直接持久化该结构; - 替代方案包括:
- 使用自定义
MarshalJSON/UnmarshalJSON手动管理指针映射(例如维护全局 ID → 实例映射表); - 改用支持引用语义的序列化格式(如
gob,但仅限 Go 内部使用); - 在结构设计层面避免依赖指针相等性,转而使用唯一 ID 字段 + 值比较。
- 使用自定义
总结:这不是 bug,而是 encoding/json 的明确设计契约。在构建可序列化的数据模型时,应始终假设指针关系在 JSON round-trip 中丢失,并基于值语义编写健壮逻辑。











