Skip to content

Instantly share code, notes, and snippets.

@abemedia
Created March 21, 2023 01:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abemedia/a0fa727e7d7ce4b06dfc7deba06296eb to your computer and use it in GitHub Desktop.
Save abemedia/a0fa727e7d7ce4b06dfc7deba06296eb to your computer and use it in GitHub Desktop.
Compare equal structs with different field order
package main_test
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestEqual(t *testing.T) {
a := struct {
Foo string
Bar string
}{"foo", "bar"}
b := struct {
Bar string
Foo string
}{"bar", "foo"}
opt := cmp.FilterPath(
func(p cmp.Path) bool { return true },
cmp.Comparer(compare),
)
if diff := cmp.Diff(a, b, opt); diff != "" {
t.Errorf(diff)
}
}
func compare(a, b any) bool {
if a == nil || b == nil {
return a == b
}
if !compareTypes(reflect.TypeOf(a), reflect.TypeOf(b)) {
return false
}
return compareValues(reflect.ValueOf(a), reflect.ValueOf(b))
}
func compareTypes(a, b reflect.Type) bool {
if a == nil || b == nil {
return a == b
}
if a.Kind() != b.Kind() {
return false
}
switch a.Kind() {
case reflect.Pointer, reflect.Slice:
return compareTypes(a.Elem(), b.Elem())
case reflect.Struct:
if a.NumField() != b.NumField() {
return false
}
af := map[string]reflect.StructField{}
bf := map[string]reflect.StructField{}
for i := 0; i < a.NumField(); i++ {
af[a.Field(i).Name] = a.Field(i)
bf[b.Field(i).Name] = b.Field(i)
}
for k := range af {
if _, ok := bf[k]; !ok {
return false
}
if af[k].Tag != bf[k].Tag {
return false
}
if !compareTypes(af[k].Type, bf[k].Type) {
return false
}
}
return true
default:
return a.Kind() == b.Kind()
}
}
func compareValues(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Pointer:
return compareValues(a.Elem(), b.Elem())
case reflect.Slice:
if a.Len() != b.Len() {
return false
}
for i := 0; i < a.Len(); i++ {
if !compareValues(a.Index(i), b.Index(i)) {
return false
}
}
return true
case reflect.Struct:
av := map[string]reflect.Value{}
bv := map[string]reflect.Value{}
for i := 0; i < a.Type().NumField(); i++ {
av[a.Type().Field(i).Name] = a.Field(i)
bv[b.Type().Field(i).Name] = b.Field(i)
}
for k := range av {
if !compareValues(av[k], bv[k]) {
return false
}
}
return true
default:
return cmp.Equal(a.Interface(), b.Interface())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment