Reputation: 431
I am comparing two structs and want to ignore a single field while doing so.
type test struct {
name string
time string
}
func main() {
a := test{"testName", time.Now().Format(time.UnixTime)}
// after some time
b := test{"testName", time.Now().Format(time.UnixTime)}
fmt.Println(a.Name == b.Name) \\ returns true Desired outcome
fmt.Println(reflect.DeepEqual(a,b)) \\ returns false
}
reflect.DeepEqual()
does not allow for us to ignore a field and have manually done the comparison one field at a time.
What is the idiomatic way to go about this?
Upvotes: 25
Views: 34326
Reputation: 2039
For testing purposes, you can use cmpopts - this allows (exported) fields to be ignored when cmp.Equal
from the cmp package is used.
func main() {
a := test{"testName", time.Now().Local().String()}
// after some time
b := test{"testName", time.Now().Local().String()}
fmt.Printf("a: %#v\nb: %#v\n", a, b)
if !cmp.Equal(a, b, cmpopts.IgnoreFields(test{}, "Time")) {
log.Fatalf("mismatching data")
}
}
Upvotes: 8
Reputation: 1
Using reflect, we can get the kind and perform a deep check on every kind.
func EqualExcept(expected, actual interface{}, exceptFields ...string) bool {
valE := reflect.ValueOf(expected)
valA := reflect.ValueOf(actual)
switch valE.Kind() {
case reflect.Array, reflect.Slice:
{
for i := 0; i < valE.Len(); i++ {
e := valE.Index(i).Interface()
a := valA.Index(i).Interface()
if !EqualExcept(e, a, exceptFields...) {
return false
}
}
return true
}
case reflect.Map:
{
for i := 0; i < len(valE.MapKeys()); i++ {
key := valE.MapKeys()[i]
keyStr := toString(key)
if stringInSlice(keyStr, exceptFields) {
continue
}
e := valE.MapIndex(key)
a := valA.MapIndex(key)
if !EqualExcept(e.Interface(), a.Interface(), exceptFields...) {
return false
}
}
return true
}
case reflect.Struct, reflect.Interface:
{
for i := 0; i < valE.NumField(); i++ {
typeField := valE.Type().Field(i)
if typeField.Tag == "json:\"-\"" || stringInSlice(typeField.Name, exceptFields) {
continue
}
e := valE.Field(i)
a := valA.FieldByName(typeField.Name)
if !EqualExcept(e.Interface(), a.Interface(), exceptFields...) {
return false
}
}
return true
}
case reflect.Ptr:
{
if valE.IsNil() {
return valA.IsNil()
}
e := valE.Elem()
a := reflect.Indirect(valA)
return EqualExcept(e.Interface(), a.Interface(), exceptFields...)
}
case reflect.String, reflect.Int, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Bool:
return expected == actual
default:
return valE.Interface() == valA.Interface()
}
}
func toString(v reflect.Value) string {
bytes, _ := json.Marshal(v.Interface())
return string(bytes)
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
Here is the demo:
Upvotes: 0
Reputation: 417592
One option would be to group fields that should take part in the comparison into a different struct which you can embed in your original. When doing the comparison, just compare the embedded fields:
type Person struct {
Name string
Age int
}
type Doc struct {
Person
Created time.Time
}
func main() {
d1 := Doc{
Person: Person{"Bob", 21},
Created: time.Now(),
}
time.Sleep(time.Millisecond)
d2 := Doc{
Person: Person{"Bob", 21},
Created: time.Now(),
}
fmt.Println(d1 == d2) // false
fmt.Println(d1.Person == d2.Person) // true
}
Try it on the Go Playground.
If the struct does not contain pointers, slices, maps etc., you can simply compare the embedded values using ==
. Otherwise use reflect.DeepEqual()
to compare them.
You may also choose to temporarily modify the fields you don't want to compare: make them equal so the comparison result will only depend on the rest of the fields:
a := test{"testName", time.Now().Format(time.StampMilli)}
time.Sleep(time.Millisecond)
b := test{"testName", time.Now().Format(time.StampMilli)}
// Save and make excluded fields equal:
old := a.time
a.time = b.time
fmt.Println(a.name == b.name) // true
fmt.Println(reflect.DeepEqual(a, b)) // true
// Restore:
a.time = old
Try it on the Go Playground.
Another variation is to make a copy of one of the struct values, and modify and compare that to the other, so no need to restore the original, also this is "more concurrent-friendly":
// Make copies and make excluded fields equal:
a2 := a
a2.time = b.time
fmt.Println(a2.name == b.name) // true
fmt.Println(reflect.DeepEqual(a2, b)) // true
Try this on the Go Playground.
If you can't or don't want to go with the above solutions, you can always create your own:
func compare(a, b test) bool {
return a.name == b.name
}
fmt.Println(a.name == b.name) // true
fmt.Println(compare(a, b)) // true
Try this on the Go Playground.
Notes:
This isn't "friendly" at first, as the custom compare()
function requires you to check all involved fields, but its implementation may use the above methods, e.g. (try it on the Go Playground):
func compare(a, b test) bool {
a.time = b.time // We're modifying a copy, so no need to make another copy
return reflect.DeepEqual(a, b)
}
You could also pass pointers to compare()
to avoid copying the original struct, e.g. (try it on the Go Playground):
fmt.Println(a.name == b.name) // true
fmt.Println(compare(&a, &b)) // true
func compare(a, b *test) bool {
a2 := new(test)
*a2 = *a
a2.time = b.time
return reflect.DeepEqual(a2, b)
}
Upvotes: 14
Reputation: 12845
If your fields are exported, you may use reflect
to check them. E.g.:
package main
import (
"fmt"
"reflect"
)
type Foo struct {
Name string
Date int
}
func (f *Foo) EqualExcept(other *Foo, ExceptField string) bool {
val := reflect.ValueOf(f).Elem()
otherFields := reflect.Indirect(reflect.ValueOf(other))
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
if typeField.Name == ExceptField {
continue
}
value := val.Field(i)
otherValue := otherFields.FieldByName(typeField.Name)
if value.Interface() != otherValue.Interface() {
return false
}
}
return true
}
func main() {
f := &Foo{
"Drew",
30,
}
f2 := &Foo{
"Drew",
50,
}
fmt.Println(f.EqualExcept(f2, "Date"))
fmt.Println(f.EqualExcept(f2, "Name"))
}
https://play.golang.org/p/DlhE8aZGwa
Upvotes: 4
Reputation: 6278
Idiomatic way would be to implement your own func (o MyStruct) Equal(o2 MyStruct) bool
method.
Upvotes: 20