
Go 的 json 包在编码/解码结构体时,会解引用指针并序列化其指向的值;反序列化时则为每个字段新建独立对象,导致原共享的指针关系丢失——这是符合文档定义的预期行为,而非 bug。
go 的 json 包在编码/解码结构体时,会解引用指针并序列化其指向的值;反序列化时则为每个字段新建独立对象,导致原共享的指针关系丢失——这是符合文档定义的预期行为,而非 bug。
在 Go 中,json.Marshal 和 json.Unmarshal 对指针类型的处理是明确且一致的:指针被“扁平化”为所指向的值进行序列化,反序列化时则为每个字段分配全新的内存地址。这意味着,即使原始结构中 n.A[0] 和 n.B[0] 指向同一个 *J 实例(即 n.A[0] == n.B[0] 为 true),经过 JSON 编码 → 文件存储 → 解码还原后,这两个切片元素将分别指向两个内容相同但地址不同的新分配的 J 实例,因此指针比较 n.A[0] == n.B[0] 必然返回 false。
这是完全符合 encoding/json 文档说明的行为:
Pointer values encode as the value pointed to.
即:指针不作为地址信息保留,而是以其解引用后的值参与序列化。
✅ 正确的语义比较方式
若业务逻辑依赖的是两个指针所指向数据的逻辑等价性(而非内存同一性),应使用值比较:
// ✅ 推荐:比较实际内容是否一致
if *n.A[0] == *n.B[0] {
fmt.Println("same logical value")
}
前提是 J 类型实现了可比较的字段组合(如所有字段均为可比较类型,且无 map、slice、func 等不可比较成员)。若 J 包含不可比较字段(如 []byte 或 map[string]int),则需自定义相等性判断函数:
func (j *J) Equal(other *J) bool {
if other == nil {
return j == nil
}
if j == nil {
return false
}
return reflect.DeepEqual(*j, *other) // 注意:reflect.DeepEqual 性能较低,生产环境建议手写比较
}
⚠️ 注意事项与替代方案
- 不要依赖 JSON 保留下层指针关系:JSON 是纯数据交换格式,不承载运行时内存拓扑信息。
- 若必须维持对象图结构(如图、循环引用、共享子对象),应避免使用标准
json包,转而考虑:- 使用
gob编码(Go 原生二进制格式,支持指针与引用保真); - 在 JSON 中手动引入 ID 映射机制(如为每个
J分配唯一ID,A/B存储 ID 列表,反序列化后重建引用); - 使用第三方库如
go-json(虽仍不恢复指针共享,但提供更精细控制)。
- 使用
? 总结
n.A[0] == n.B[0] 在 JSON round-trip 后变为 false 是设计使然,不是缺陷。开发者应区分「内存同一性」(== 比较指针)和「数据等价性」(值比较或自定义 Equal 方法),并在序列化场景中始终基于后者构建逻辑。坚持这一原则,可写出更健壮、可移植的 Go 数据持久化代码。











