在Golang开发中,判断两个复杂数据结构是否完全相等是常见需求,部分开发者会选择先将两个结构序列化为JSON字符串再比较字符串是否一致的方式实现深度相等性测试,这种方式看似便捷,实际存在多个容易被忽略的陷阱。

陷阱一:数值类型序列化后精度丢失
JSON的规范中数值类型没有区分整数和浮点数,当结构中存在大整数或者特定浮点数时,序列化后可能出现精度变化,导致比较结果不符合预期。
比如下面的示例,int64类型的大整数在JSON序列化后转回字符串时,部分场景下会出现精度丢失:
package main
import (
"encoding/json"
"fmt"
)
type Data struct {
Num int64
}
func main() {
a := Data{Num: 9223372036854775807}
b := Data{Num: 9223372036854775807}
aBytes, _ := json.Marshal(a)
bBytes, _ := json.Marshal(b)
fmt.Println(string(aBytes) == string(bBytes)) // 部分场景下可能返回false
}
陷阱二:零值和空值的处理差异
结构中的零值和空值在JSON序列化时的表现不同,会导致相等性判断出错。比如指针类型的零值是nil,切片类型的零值是nil,而空切片序列化后的结果和nil序列化后的结果并不一致。
以下示例展示了nil切片和空切片的序列化差异:
package main
import (
"encoding/json"
"fmt"
)
type List struct {
Items []int
}
func main() {
a := List{Items: nil}
b := List{Items: []int{}}
aBytes, _ := json.Marshal(a)
bBytes, _ := json.Marshal(b)
fmt.Println(string(aBytes)) // 输出 {"Items":null}
fmt.Println(string(bBytes)) // 输出 {"Items":[]}
fmt.Println(string(aBytes) == string(bBytes)) // 输出 false
}
陷阱三:无法处理循环引用结构
如果待比较的结构中存在循环引用,JSON序列化时会直接抛出错误,无法完成后续的比较操作。比如一个节点结构指向自己,序列化时就会陷入无限递归。
package main
import (
"encoding/json"
"fmt"
)
type Node struct {
Val int
Next *Node
}
func main() {
a := &Node{Val: 1}
a.Next = a // 循环引用
_, err := json.Marshal(a)
fmt.Println(err) // 输出 json: unsupported value: encountered a cycle via *main.Node
}
陷阱四:字段顺序影响比较结果
Go的json.Marshal方法序列化结构体时,字段顺序是按照结构体定义顺序排列的,但如果待比较的两个结构是通过不同方式生成的map类型,map的遍历顺序是不确定的,序列化后的JSON字符串顺序也会不同,导致字符串比较返回false。
package main
import (
"encoding/json"
"fmt"
)
func main() {
a := map[string]int{"x": 1, "y": 2}
b := map[string]int{"y": 2, "x": 1}
aBytes, _ := json.Marshal(a)
bBytes, _ := json.Marshal(b)
// 两次运行可能得到不同的顺序,比较结果不确定
fmt.Println(string(aBytes) == string(bBytes))
}
陷阱五:私有字段和自定义序列化逻辑干扰
结构体中的私有字段不会被JSON序列化,如果结构依赖私有字段区分相等性,那么JSON序列化方式会忽略这部分信息。另外如果结构体实现了MarshalJSON方法,自定义了序列化逻辑,也可能导致序列化后的结果和实际的相等性判断逻辑不符。
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string
age int // 私有字段,不会被序列化
}
func main() {
a := User{Name: "test", age: 10}
b := User{Name: "test", age: 20}
aBytes, _ := json.Marshal(a)
bBytes, _ := json.Marshal(b)
fmt.Println(string(aBytes) == string(bBytes)) // 返回 true,但实际两个结构不相等
}
更合适的深度相等性测试方案
Go标准库的reflect包提供了DeepEqual方法,可以直接比较两个值是否深度相等,避免了JSON序列化的各类陷阱。不过DeepEqual也有自身的注意点,比如它会比较类型是否完全一致,零值和空值的判断更符合Go的语义。
package main
import (
"fmt"
"reflect"
)
type List struct {
Items []int
}
func main() {
a := List{Items: nil}
b := List{Items: []int{}}
fmt.Println(reflect.DeepEqual(a, b)) // 输出 false,符合预期
}
总结来说,使用JSON序列化进行深度相等性测试只适合非常简单的、无循环引用、无特殊类型、无私有字段的场景,大多数业务场景下更推荐使用reflect.DeepEqual,或者根据业务需求自定义相等性判断逻辑,避免落入上述陷阱。
GolangJSON序列化深度相等性测试reflect_DeepEqual修改时间:2026-07-01 04:09:37