How to use go-сmp to compare such structs?

Viewed 1444

I have the following simplified set of structs

type S0 struct {
  UUID uuid.UUID
  F00  int
  F01  int
}

type S1 struct {
  S0
  F10 int
  F11 int 
}

type S2 struct {
  S0
  F20 int
}

<...>

type SN struct {
  S0
  FN0 int
  <...>
  FNM int
}

A struct S0 is equal to another struct S0 if all their fields(UUID, F0i) are equal. The struct Sk (k=1..N) is equal to the struct Sk, if their parts Sk.S0 are equal, even if all their fields Fki are not equal. But if their parts Sk.S0 are not equal, then structs will still be equal, if all their fields Fki are equal. Struct S0 is equal to any other struct Sk if Sk.S0 == S0. The struct Si is never equal to the struct Sj (i != j), even if Si.S0 == Sj.S0(In theory, this is generally unrealistic in current architecture, but just in case). All structs implement a common interface I. I want to write the following equality function using go-cmp.

func EqualI(i1 I, i2 I) bool

I wrote the following comparison functions for each struct, which looks about the same, but how to combine them into one function EqualI and not get an error 'ambiguous set of applicable options'?

func EqualS0(s0_a S0, s0_b S0) bool {
  return cmp.Equal(s0_a, s0_b)
}

func EqualS1(s1_a S1, s1_b S1) bool {
  if EqualS0(s1_a.S0, s1_b.S0) {
    return true
  }

  return cmp.Equal(s1_a, s1_b, cmpopts.IgnoreFields(S1{}, "S0.UUID", "S0.F00", "S0.F01"))
}
2 Answers

Good Day!

Here is the sample code using the map[string]interface. You can also see it here.

package main

import (
    "fmt"
    "reflect"
    "sort"
)

func main() {
    
    one := map[string]interface{} {
        "uuid":"123-456-789",
        "F00":0,
        "F01":1,
        "F10":3,
    }
    
    other := map[string]interface{} {
        "uuid":"987-654-321",
        "F00": 2,
        "F01": 3,
        "F10": 3,
    }
    
    fmt.Println("Result of Full Comparison = ", compare(one, other))
    
    fmt.Println("Result of Part Comparison = ", compareIgnoreSome(one,other, []string{"uuid","F00","F01"}))
}

func compare(a map[string]interface{}, b map[string]interface{}) bool {

    for k1,v1 := range a{
        if v2,exists := b[k1]; !exists {
            return false
        } else {
        
            if !reflect.DeepEqual(v1, v2) {
                return false
            }
        }
        
    }
    return true
}




func compareIgnoreSome(a map[string]interface{}, b map[string]interface{}, some []string) bool {
    sort.Strings(some) 

    
    for k1,v1 := range a{
        if i := sort.SearchStrings(some, k1); i>=0 {            
            continue
        } 
        
        fmt.Println("target key= ", k1)
        if v2,exists := b[k1]; !exists {
            return false
        } else {
        
            if !reflect.DeepEqual(v1, v2) {
            
                fmt.Printf("Diff %v, %v\n", v1, v2)
                return false
            }
        }
        
    }
    return true
}

is better do not to use the cmp package for production, I quote:

It is intended to only be used in tests, as performance is not a goal and it may panic if it cannot compare the values.

Suppose you have the following interface implemented by S0...SN:

type I interface {
    Foo()
}

You can write EqualI considering your requirements like follows

import "reflect"

func EqualI(i1 I, i2 I) bool {
    v1 := reflect.ValueOf(i1)
    v2 := reflect.ValueOf(i2)
    t1 := v1.Type()

    // if i1, i2 are not of the same type
    if t1 != v2.Type() {
        return false
    }

    // If i1,i2 are S0 structs
    if s00, ok := i1.(S0); ok {
        return reflect.DeepEqual(s00, i2.(S0))
    }

    // If i1,i2 are not S0 structs
    // then check for the SO field
    fieldS00 := v1.Field(0).Interface().(S0)
    fieldS01 := v2.Field(0).Interface().(S0)
    if reflect.DeepEqual(fieldS00, fieldS01) {
        return true
    }

    // in other case check the rest of fields
    for i := 1; i < t1.NumField(); i++ {
        if !reflect.DeepEqual(v1.Field(i).Interface(), v2.Field(i).Interface()) {
            return false
        }
    }

    return true
}

With the reflect package you can access a struct member at runtime.

Notes

The order is very important for K=1..N, SK struct, every SK must have S0 as the first field:

type SK struct {
  S0
  // your fields ...    
}

GoLang Playground

Related