Go 的 reflect 可实现可配置深度比较器,支持忽略字段、调用 Equal 方法、浮点容差比较等;而 reflect.DeepEqual 不支持这些定制,且对函数、NaN、不可比较 map 键值会 panic。
用 Go 的 reflect 写通用深度比较函数,核心是递归遍历两个值的结构,逐字段或逐元素比对。标准库的 reflect.DeepEqual 已经做了这件事,但若需自定义行为(比如忽略某些字段、处理 NaN、支持自定义 Equal 方法、跳过未导出字段等),就得自己实现。
它能处理大多数情况:结构体、切片、map、指针、接口、基本类型等。但它不处理:
Equal(other T) bool 方法(即使存在)关键思路:用 reflect.Value 获取值的种类(Kind),按类型分治处理,并支持选项控制行为。
基础骨架如下:
type Comparer struct {
ignoreFields map[string]bool
useEqualMethod bool
skipUnexported bool
}
func (c *Comparer) Equal(a, b interface{}) bool {
return c.equalValue(reflect.ValueOf(a), reflect.ValueOf(b))
}
func (c *Comparer) equalValue(v1, v2 reflect.Value) bool {
// 处理零值、不同 Kind、不同类型等前置检查
if v1.Kind() != v2.Kind() {
return false
}
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
switch v1.Kind() {
case reflect.Struct:
return c.equalStruct(v1, v2)
case reflect.Slice, reflect.Array:
return c.equalSlice(v1,
v2)
case reflect.Map:
return c.equalMap(v1, v2)
case reflect.Ptr:
return c.equalPtr(v1, v2)
case reflect.Interface:
return c.equalInterface(v1, v2)
case reflect.Float32, reflect.Float64:
return c.equalFloat(v1, v2)
default:
return v1.Interface() == v2.Interface()
}
}
结构体字段跳过:遍历字段前检查字段名是否在 ignoreFields 中;同时可通过 struct tag(如 json:"-" or diff:"skip")动态控制。
支持 Equal 方法:若任一值有 Equal(interface{}) bool 方法,优先调用它(需确保参数类型兼容)。
浮点数容差比较:不直接用 ==,改用 math.Abs(a-b) ,尤其对 NaN 单独判断(math.IsNaN)。
指针解引用策略:默认解引用比较(nil 指针视为相等),也可提供选项保留指针身份比较(即地址相同才等)。
v.IsValid() 检查,避免 panic(如 nil 接口、空指针解引用)v.Type().Field(i) 获取 tagmap[uintptr]bool 记录已访问地址)基本上就这些。写一个够用的通用比较器不复杂,但容易忽略循环引用、NaN、方法调用一致性等细节。从 reflect.DeepEqual 源码入手读一遍,再按需扩展,是最稳妥的路径。