The cmpopts package provides common pre-built options for the cmp package, covering typical comparison scenarios like approximate equality, sorting, and selective ignoring.
import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)func EquateEmpty() cmp.OptionEquateEmpty returns a cmp.Comparer option that determines all maps and slices with a length of zero to be equal, regardless of whether they are nil.
Returns:
cmp.Option - Configuration optionExample:
var nilSlice []int
emptySlice := []int{}
cmp.Equal(nilSlice, emptySlice) // false (different)
cmp.Equal(nilSlice, emptySlice, cmpopts.EquateEmpty()) // true (both empty)
var nilMap map[string]int
emptyMap := map[string]int{}
cmp.Equal(nilMap, emptyMap) // false (different)
cmp.Equal(nilMap, emptyMap, cmpopts.EquateEmpty()) // true (both empty)func EquateApprox(fraction, margin float64) cmp.OptionEquateApprox returns a cmp.Comparer option that determines float32 or float64 values to be equal if they are within a relative fraction or absolute margin. Not used when either x or y is NaN or infinite.
Parameters:
fraction float64 - Relative fraction tolerance (use 0 for none). Must be non-negative.margin float64 - Absolute margin tolerance (use 0 for none). Must be non-negative.Returns:
cmp.Option - Configuration optionFormula: |x-y| ≤ max(fraction*min(|x|, |y|), margin)
Note: Can be used in conjunction with EquateNaNs.
Example:
// Within 5% or 0.1 absolute difference
approx := cmpopts.EquateApprox(0.05, 0.1)
cmp.Equal(1.0, 1.03, approx) // true (within 5%)
cmp.Equal(0.0, 0.05, approx) // true (within 0.1 margin)
cmp.Equal(1.0, 1.2, approx) // false (exceeds both)
// Only absolute margin
absoluteOnly := cmpopts.EquateApprox(0, 0.01)
cmp.Equal(1.001, 1.002, absoluteOnly) // true
// Only relative fraction
relativeOnly := cmpopts.EquateApprox(0.01, 0)
cmp.Equal(100.0, 100.5, relativeOnly) // true (within 1%)func EquateApproxTime(margin time.Duration) cmp.OptionEquateApproxTime returns a cmp.Comparer option that determines two non-zero time.Time values to be equal if they are within some margin of one another. If both times have a monotonic clock reading, uses the monotonic time difference.
Parameters:
margin time.Duration - Time margin. Must be non-negative.Returns:
cmp.Option - Configuration optionExample:
import "time"
now := time.Now()
slightlyLater := now.Add(100 * time.Millisecond)
// Within 1 second
timeTolerance := cmpopts.EquateApproxTime(time.Second)
cmp.Equal(now, slightlyLater, timeTolerance) // true
cmp.Equal(now, now.Add(2*time.Second), timeTolerance) // falsefunc EquateNaNs() cmp.OptionEquateNaNs returns a cmp.Comparer option that determines float32 and float64 NaN values to be equal.
Returns:
cmp.Option - Configuration optionNote: Can be used in conjunction with EquateApprox.
Example:
import "math"
nan1 := math.NaN()
nan2 := math.NaN()
cmp.Equal(nan1, nan2) // false (NaN != NaN in Go)
cmp.Equal(nan1, nan2, cmpopts.EquateNaNs()) // truefunc EquateComparable(typs ...interface{}) cmp.OptionEquateComparable returns a cmp.Option that determines equality of comparable types by directly comparing them using the == operator in Go. The types to compare are specified by passing a value of that type.
Parameters:
typs ...interface{} - Types to compare (specified by passing value of each type)Returns:
cmp.Option - Configuration optionNote: Only use on types documented as safe for direct == comparison. For example, net/netip.Addr is safe, while time.Time discourages use of ==.
Example:
import "net/netip"
addr1 := netip.MustParseAddr("192.168.1.1")
addr2 := netip.MustParseAddr("192.168.1.1")
// netip.Addr is documented as safe for == comparison
cmp.Equal(addr1, addr2, cmpopts.EquateComparable(netip.Addr{})) // truefunc EquateErrors() cmp.OptionEquateErrors returns a cmp.Comparer option that determines errors to be equal if errors.Is reports them to match. The AnyError error can be used to match any non-nil error.
Returns:
cmp.Option - Configuration optionExample:
import (
"errors"
"io"
)
err1 := io.EOF
err2 := io.EOF
wrappedEOF := fmt.Errorf("wrapped: %w", io.EOF)
cmp.Equal(err1, err2, cmpopts.EquateErrors()) // true
cmp.Equal(err1, wrappedEOF, cmpopts.EquateErrors()) // true (errors.Is matches)
// Using AnyError
type Response struct {
Data string
Err error
}
r1 := Response{Data: "test", Err: errors.New("failed")}
r2 := Response{Data: "test", Err: errors.New("other error")}
// Match any non-nil error
cmp.Equal(r1, r2, cmp.Comparer(func(x, y error) bool {
if x == cmpopts.AnyError || y == cmpopts.AnyError {
return x != nil && y != nil
}
return errors.Is(x, y) || errors.Is(y, x)
}))var AnyError anyErrorAnyError is an error that matches any non-nil error. Used with EquateErrors and custom error comparisons.
func IgnoreUnexported(typs ...interface{}) cmp.OptionIgnoreUnexported returns a cmp.Option that ignores the immediate unexported fields of a struct, including anonymous fields of unexported types. Unexported fields within exported fields of struct types are not ignored unless their type is also passed.
Parameters:
typs ...interface{} - Struct types whose unexported fields should be ignoredReturns:
cmp.Option - Configuration optionWarning: Avoid ignoring unexported fields of types you don't control (e.g., from another repository), as implementation changes may affect behavior. Prefer custom cmp.Comparer instead.
Example:
type Person struct {
Name string
age int // unexported
}
p1 := Person{Name: "Alice", age: 30}
p2 := Person{Name: "Alice", age: 35}
// Would panic without option
cmp.Equal(p1, p2, cmpopts.IgnoreUnexported(Person{})) // true (age ignored)func IgnoreFields(typ interface{}, names ...string) cmp.OptionIgnoreFields returns a cmp.Option that ignores fields of the given names on a single struct type. Respects names of exported fields forwarded due to struct embedding.
Parameters:
typ interface{} - Struct type (specified by passing value of that type)names ...string - Field names to ignore. May be dot-delimited for nested fields (e.g., "Foo.Bar")Returns:
cmp.Option - Configuration optionExample:
type User struct {
ID int
Name string
Password string
Email string
}
u1 := User{ID: 1, Name: "Alice", Password: "secret1", Email: "alice@example.com"}
u2 := User{ID: 1, Name: "Alice", Password: "secret2", Email: "alice@example.com"}
// Ignore Password field
cmp.Equal(u1, u2, cmpopts.IgnoreFields(User{}, "Password")) // trueExample with nested fields:
type Address struct {
Street string
City string
}
type Employee struct {
Name string
Address Address
}
e1 := Employee{Name: "Bob", Address: Address{Street: "1st St", City: "NYC"}}
e2 := Employee{Name: "Bob", Address: Address{Street: "2nd St", City: "NYC"}}
// Ignore nested Street field
cmp.Equal(e1, e2, cmpopts.IgnoreFields(Employee{}, "Address.Street")) // truefunc IgnoreTypes(typs ...interface{}) cmp.OptionIgnoreTypes returns a cmp.Option that ignores all values assignable to certain types.
Parameters:
typs ...interface{} - Types to ignore (specified by passing value of each type)Returns:
cmp.Option - Configuration optionExample:
import "time"
type Event struct {
Name string
Timestamp time.Time
Details string
}
e1 := Event{Name: "login", Timestamp: time.Now(), Details: "User logged in"}
e2 := Event{Name: "login", Timestamp: time.Now().Add(time.Hour), Details: "User logged in"}
// Ignore all time.Time values
cmp.Equal(e1, e2, cmpopts.IgnoreTypes(time.Time{})) // truefunc IgnoreInterfaces(ifaces interface{}) cmp.OptionIgnoreInterfaces returns a cmp.Option that ignores all values or references of values assignable to certain interface types. Interfaces specified by passing anonymous struct with interface types embedded.
Parameters:
ifaces interface{} - Anonymous struct with embedded interface types (e.g., struct{sync.Locker}{})Returns:
cmp.Option - Configuration optionExample:
import (
"io"
"sync"
)
type Config struct {
Name string
Lock sync.Locker
Reader io.Reader
}
c1 := Config{Name: "cfg", Lock: &sync.Mutex{}, Reader: nil}
c2 := Config{Name: "cfg", Lock: &sync.RWMutex{}, Reader: nil}
// Ignore sync.Locker interface
cmp.Equal(c1, c2, cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})) // truefunc IgnoreSliceElements(discardFunc interface{}) cmp.OptionIgnoreSliceElements returns a cmp.Option that ignores elements of []V. Elements are ignored if the function reports true.
Parameters:
discardFunc interface{} - Must be function func(T) bool for slice elements V assignable to TReturns:
cmp.Option - Configuration optionExample:
numbers := []int{1, 2, 0, 3, 0, 4}
filtered := []int{1, 2, 3, 4}
// Ignore zero elements
ignoreZeros := cmpopts.IgnoreSliceElements(func(x int) bool {
return x == 0
})
cmp.Equal(numbers, filtered, ignoreZeros) // true (zeros ignored)func IgnoreMapEntries(discardFunc interface{}) cmp.OptionIgnoreMapEntries returns a cmp.Option that ignores entries of map[K]V. Entries are ignored if the function reports true.
Parameters:
discardFunc interface{} - Must be function func(T, R) bool for map keys K assignable to T and values V assignable to RReturns:
cmp.Option - Configuration optionExample:
m1 := map[string]int{"a": 1, "b": 2, "temp": 99}
m2 := map[string]int{"a": 1, "b": 2}
// Ignore entries with keys starting with "temp"
ignoreTempKeys := cmpopts.IgnoreMapEntries(func(k string, v int) bool {
return strings.HasPrefix(k, "temp")
})
cmp.Equal(m1, m2, ignoreTempKeys) // true (temp entry ignored)func SortSlices(lessOrCompareFunc interface{}) cmp.OptionSortSlices returns a cmp.Transformer option that sorts all []V slices before comparison.
Parameters:
lessOrCompareFunc interface{} - Either:
func(T, T) bool for slice elements V assignable to Tfunc(T, T) int for slice elements V assignable to TReturns:
cmp.Option - Configuration optionLess function requirements:
less(x, y) == less(x, y)!less(x, x)!less(x, y) and !less(y, z), then !less(x, z)Compare function requirements:
compare(x, y) == compare(x, y)compare(x, x) == 0compare(x, y) < 0 and compare(y, z) < 0, then compare(x, z) < 0Note: Does not need to be "total" - relative order maintained if inequality not reported. Can be used with EquateEmpty.
Example:
a := []int{3, 1, 2}
b := []int{1, 2, 3}
sortInts := cmpopts.SortSlices(func(x, y int) bool {
return x < y
})
cmp.Equal(a, b, sortInts) // true (sorted before comparison)Example with structs:
type Person struct {
Name string
Age int
}
people1 := []Person{{Name: "Bob", Age: 30}, {Name: "Alice", Age: 25}}
people2 := []Person{{Name: "Alice", Age: 25}, {Name: "Bob", Age: 30}}
sortByName := cmpopts.SortSlices(func(x, y Person) bool {
return x.Name < y.Name
})
cmp.Equal(people1, people2, sortByName) // truefunc SortMaps(lessOrCompareFunc interface{}) cmp.OptionSortMaps returns a cmp.Transformer option that flattens map[K]V types to sorted []struct{K, V}. Allows cmp.Comparer options to be used on map keys or using K.Equal method if it exists.
Parameters:
lessOrCompareFunc interface{} - Either:
func(T, T) bool for map keys K assignable to Tfunc(T, T) int for map keys K assignable to TReturns:
cmp.Option - Configuration optionLess function requirements:
less(x, y) == less(x, y)!less(x, x)!less(x, y) and !less(y, z), then !less(x, z)x != y, then either less(x, y) or less(y, x)Compare function requirements:
compare(x, y) == compare(x, y)compare(x, x) == 0compare(x, y) < 0 and compare(y, z) < 0, then compare(x, z) < 0x != y, then compare(x, y) != 0Note: Can be used with EquateEmpty.
Example:
m1 := map[string]int{"c": 3, "a": 1, "b": 2}
m2 := map[string]int{"a": 1, "b": 2, "c": 3}
sortMapKeys := cmpopts.SortMaps(func(x, y string) bool {
return x < y
})
cmp.Equal(m1, m2, sortMapKeys) // true (order doesn't matter)func AcyclicTransformer(name string, xformFunc interface{}) cmp.OptionAcyclicTransformer returns a cmp.Transformer with a filter applied that ensures the transformer cannot be recursively applied upon its own output.
Parameters:
name string - Name for the transformerxformFunc interface{} - Transformation functionReturns:
cmp.Option - Configuration optionUse Case: Prevents infinite cycles when transformer input and output types are the same or when output could be transformed again.
Example:
// Split string by lines - would cause infinite cycle with regular Transformer
splitLines := cmpopts.AcyclicTransformer("SplitLines", func(s string) []string {
return strings.Split(s, "\n")
})
text1 := "line1\nline2\nline3"
text2 := "line1\nline2\nline3"
cmp.Equal(text1, text2, splitLines) // Compares as []string without cycles