2

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"))
}
JohnnyIpcom
  • 21
  • 1
  • 3

2 Answers2

0

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
}
JC Ahn
  • 336
  • 1
  • 13
  • You know, this might be a good idea. But in a slightly different version, it is still much more convenient to use structs in the rest of the code. But such a complex comparison is needed only in one place, before adding/retrieving these structs to the MongoDB storage in order to avoid duplicates. After all, you can store and restore structs from the repository in any form. I know there is a nice library mapstructure that allows you to convert struct to map. I've never used it, but it can help. Thank you, I did not think about this question from this side. – JohnnyIpcom Mar 23 '21 at 20:30
  • Okay, It's slightly different. I think,you would better use a mapstruct library. – JC Ahn Mar 24 '21 at 07:08
0

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