
Go 的 json 包在编码/解码结构体时会解引用指针,导致原本指向同一对象的多个指针在反序列化后变为独立副本;这是符合规范的预期行为,可通过值比较或自定义编组逻辑解决。
go 的 `json` 包在编码/解码结构体时会解引用指针,导致原本指向同一对象的多个指针在反序列化后变为独立副本;这是符合规范的预期行为,可通过值比较或自定义编组逻辑解决。
在 Go 中,encoding/json 包对指针的处理遵循明确的设计原则:指针在 JSON 编码时被“展开”为其所指向的值,解码时则创建全新对象并赋值给新分配的指针。这意味着原始内存地址关系(如 n.A[0] == n.B[0])必然丢失——因为 JSON 本身不携带内存地址信息,也无法表达引用关系。
例如,假设原始结构体中 n.A[0] 和 n.B[0] 均指向同一个 *J 实例:
j := &J{Value: 42}
n := &N{
A: []*J{j},
B: []*J{j}, // 同一地址
}
fmt.Println("is same pointer", n.A[0] == n.B[0]) // true经 json.Marshal(n) → 文件存储 → json.Unmarshal(bytes, &n) 后,n.A[0] 和 n.B[0] 将分别指向两个内容相同但地址不同的 *J 实例,因此指针比较返回 false。
✅ 这是完全符合文档的行为:
官方文档明确说明:
"Pointer values encode as the value pointed to."
(指针值编码为其所指向的值)
✅ 正确的校验方式:比较值而非地址
若业务逻辑依赖的是数据一致性而非内存同一性,应使用解引用比较:
if n.A != nil && n.B != nil && len(n.A) > 0 && len(n.B) > 0 {
fmt.Println("same value?", *n.A[0] == *n.B[0]) // ✅ 推荐:值相等性检查
}⚠️ 注意:这要求 J 类型实现了可比性(即所有字段均可比较,无 map、func、slice 等不可比较类型)。若 J 含不可比较字段,可改用 reflect.DeepEqual:
fmt.Println("same value?", reflect.DeepEqual(n.A[0], n.B[0])) // 安全通用? 进阶方案:保留引用语义(按需)
若确实需要重建共享引用(例如图结构、缓存节点复用),需脱离标准 JSON 流程,采用以下策略之一:
-
ID 映射法:序列化时为每个
J分配唯一 ID,A/B存储 ID 列表;反序列化后通过 ID 查找并复用同一*J实例。 -
自定义
MarshalJSON/UnmarshalJSON:在N或J上实现,手动控制指针序列化逻辑(但需谨慎,易引入复杂性)。 -
使用
gob替代 JSON:gob是 Go 原生二进制格式,支持指针和引用保真,但牺牲跨语言兼容性。
? 总结
- JSON 是数据交换格式,不承诺保留 Go 运行时的内存语义(包括指针、切片底层数组、channel 等);
- 指针比较失效是正常现象,不应视为 bug;
- 优先通过值相等性(
*p == *q或DeepEqual)验证业务逻辑正确性; - 如需引用复用,请显式建模(如 ID 索引)或切换序列化机制。
始终牢记:JSON 处理的是数据,不是对象图。


















