The cmp package provides a powerful option system for customizing comparison behavior through Comparers, Transformers, and Filters.
import "github.com/google/go-cmp/cmp"func Comparer(f interface{}) OptionComparer returns an Option that determines whether two values are equal using a custom equality function.
Parameters:
f interface{} - Must be a function func(T, T) bool. Implicitly filtered to input values assignable to T. If T is an interface, f may be called with different concrete types that implement T.Returns:
Option - Configuration optionRequirements: The equality function must be:
equal(x, y) == equal(y, x)equal(x, y) always returns the same resultequal(x, y) does not modify x or yExample - Approximate Float Comparison:
// Compare floats within tolerance
approxEqual := cmp.Comparer(func(x, y float64) bool {
tolerance := 0.01
return math.Abs(x-y) < tolerance
})
a := 1.0001
b := 1.0002
cmp.Equal(a, b, approxEqual) // returns trueExample - Custom Type Comparison:
type Version struct {
Major, Minor, Patch int
}
// Compare versions by semantic version rules
versionComparer := cmp.Comparer(func(x, y Version) bool {
return x.Major == y.Major &&
x.Minor == y.Minor &&
x.Patch == y.Patch
})
v1 := Version{1, 2, 3}
v2 := Version{1, 2, 3}
cmp.Equal(v1, v2, versionComparer) // returns trueExample - String Case-Insensitive Comparison:
caseInsensitive := cmp.Comparer(func(x, y string) bool {
return strings.EqualFold(x, y)
})
cmp.Equal("Hello", "hello", caseInsensitive) // returns truefunc Transformer(name string, f interface{}) OptionTransformer returns an Option that applies a transformation function that converts values of one type to another before comparison.
Parameters:
name string - User-provided label for the transformation. Must be a valid Go identifier or qualified identifier. If empty, an arbitrary name is used. Appears in Diff output.f interface{} - Must be a function func(T) R that converts values of type T to type R. Implicitly filtered to input values assignable to T. Must not mutate T.Returns:
Option - Configuration optionCycle Prevention: Implicit filter prevents transformer from being applied to its own output (e.g., when T and R are the same type). For more control, use cmpopts.AcyclicTransformer.
Example - Compare Structs by Specific Fields:
type Person struct {
Name string
Age int
ID string // Don't care about ID in comparison
}
// Transform to only name and age
nameAndAge := cmp.Transformer("NameAndAge", func(p Person) struct{ Name string; Age int } {
return struct{ Name string; Age int }{p.Name, p.Age}
})
p1 := Person{Name: "Alice", Age: 30, ID: "123"}
p2 := Person{Name: "Alice", Age: 30, ID: "456"}
cmp.Equal(p1, p2, nameAndAge) // returns true (IDs ignored)Example - Normalize Before Comparison:
// Normalize strings by trimming and lowercasing
normalize := cmp.Transformer("Normalize", func(s string) string {
return strings.ToLower(strings.TrimSpace(s))
})
cmp.Equal(" Hello ", "hello", normalize) // returns trueExample - Sort Slices Before Comparison:
// Transform slice to sorted slice
sortInts := cmp.Transformer("SortInts", func(s []int) []int {
sorted := append([]int(nil), s...)
sort.Ints(sorted)
return sorted
})
a := []int{3, 1, 2}
b := []int{1, 2, 3}
cmp.Equal(a, b, sortInts) // returns truefunc Ignore() OptionIgnore is an Option that causes all comparisons to be ignored.
Returns:
Option - Configuration optionImportant: This value is intended to be combined with FilterPath or FilterValues. It is an error to pass an unfiltered Ignore option to Equal.
Example - Ignore Specific Fields:
type Record struct {
ID int
Timestamp time.Time
Data string
}
// Ignore timestamp field
ignoreTimestamp := cmp.FilterPath(func(p cmp.Path) bool {
return p.String() == "Timestamp"
}, cmp.Ignore())
r1 := Record{ID: 1, Timestamp: time.Now(), Data: "test"}
r2 := Record{ID: 1, Timestamp: time.Now().Add(time.Hour), Data: "test"}
cmp.Equal(r1, r2, ignoreTimestamp) // returns true (timestamps ignored)func FilterPath(f func(Path) bool, opt Option) OptionFilterPath returns a new Option where opt is only evaluated if filter f returns true for the current Path in the value tree.
Parameters:
f func(Path) bool - Filter function that examines the current path. Must be symmetric (identical result regardless of whether missing value is from x or y).opt Option - Option to conditionally apply. May be Ignore, Transformer, Comparer, Options, or previously filtered Option.Returns:
Option - Filtered configuration optionNote: Called even if slice element or map entry is missing, providing opportunity to ignore such cases.
Example - Ignore Field by Path:
type Config struct {
Name string
Internal struct {
Secret string
Debug bool
}
}
// Ignore Internal.Secret field
ignoreSecret := cmp.FilterPath(func(p cmp.Path) bool {
return p.String() == "Internal.Secret"
}, cmp.Ignore())
c1 := Config{Name: "prod", Internal: struct{Secret string; Debug bool}{"key1", false}}
c2 := Config{Name: "prod", Internal: struct{Secret string; Debug bool}{"key2", false}}
cmp.Equal(c1, c2, ignoreSecret) // returns trueExample - Apply Comparer to Specific Path:
type Data struct {
Exact float64
Approx float64
}
// Apply approximate comparison only to Approx field
approxForApproxField := cmp.FilterPath(
func(p cmp.Path) bool {
return p.String() == "Approx"
},
cmp.Comparer(func(x, y float64) bool {
return math.Abs(x-y) < 0.01
}),
)
d1 := Data{Exact: 1.5, Approx: 2.001}
d2 := Data{Exact: 1.5, Approx: 2.002}
cmp.Equal(d1, d2, approxForApproxField) // returns truefunc FilterValues(f interface{}, opt Option) OptionFilterValues returns a new Option where opt is only evaluated if filter f returns true for the current pair of values being compared.
Parameters:
f interface{} - Must be a function func(T, T) bool. Implicitly returns false if either value is invalid or not assignable to T. Must be symmetric and deterministic. If T is an interface, may be called with different concrete types that implement T.opt Option - Option to conditionally apply. May be Ignore, Transformer, Comparer, Options, or previously filtered Option.Returns:
Option - Filtered configuration optionExample - Ignore Zero Values:
// Ignore comparison when both values are empty strings
ignoreEmptyStrings := cmp.FilterValues(func(x, y string) bool {
return x == "" && y == ""
}, cmp.Ignore())
type User struct {
Name string
Nickname string
}
u1 := User{Name: "Alice", Nickname: ""}
u2 := User{Name: "Alice", Nickname: ""}
cmp.Equal(u1, u2, ignoreEmptyStrings) // returns trueExample - Conditional Comparer:
// Only use approximate comparison for values > 1000
largeNumberApprox := cmp.FilterValues(func(x, y float64) bool {
return x > 1000 || y > 1000
}, cmp.Comparer(func(x, y float64) bool {
return math.Abs(x-y) / math.Max(x, y) < 0.01 // 1% tolerance
}))
cmp.Equal(1000.0, 1005.0, largeNumberApprox) // uses exact comparison, returns false
cmp.Equal(2000.0, 2010.0, largeNumberApprox) // uses approximate, returns trueExample - Type-Specific Handling:
import "time"
// Ignore time comparisons when either is zero
ignoreZeroTime := cmp.FilterValues(func(x, y time.Time) bool {
return x.IsZero() || y.IsZero()
}, cmp.Ignore())
t1 := time.Time{} // zero time
t2 := time.Now()
type Event struct {
Name string
Timestamp time.Time
}
e1 := Event{Name: "test", Timestamp: t1}
e2 := Event{Name: "test", Timestamp: t2}
cmp.Equal(e1, e2, ignoreZeroTime) // returns true (zero time ignored)Multiple options can be combined by passing them all to Equal or Diff:
result := cmp.Equal(x, y,
cmpopts.IgnoreUnexported(MyType{}),
cmpopts.EquateEmpty(),
cmp.Comparer(customCompare),
cmp.Transformer("MyTransform", myTransform),
)Options can also be grouped using the Options type:
type Options []OptionExample:
standardOpts := cmp.Options{
cmpopts.IgnoreUnexported(Config{}),
cmpopts.EquateEmpty(),
}
cmp.Equal(x, y, standardOpts)