Skip to content

Instantly share code, notes, and snippets.

@StevenACoffman
Last active July 26, 2024 19:23

Revisions

  1. StevenACoffman revised this gist Jul 26, 2024. 1 changed file with 20 additions and 1 deletion.
    21 changes: 20 additions & 1 deletion StructCompareExceptOneField.md
    Original file line number Diff line number Diff line change
    @@ -78,6 +78,23 @@ If you can't or don't want to go with the above solutions, you can always create

    Try this on the [Go Playground][4].

    ## 4. Use a library like github.com/google/go-cmp/cmp/cmpopts

    For testing purposes, you can use [cmpopts][7] - this allows (exported) fields to be ignored when `cmp.Equal` from the [cmp][8] 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")
    }
    }


    **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][5]):
    @@ -105,4 +122,6 @@ You could also pass pointers to `compare()` to avoid copying the original struct
    [3]: https://play.golang.org/p/rIOBVWBOhB
    [4]: https://play.golang.org/p/LJ4cVhyGui
    [5]: https://play.golang.org/p/jRxOQyPXAM
    [6]: https://play.golang.org/p/4xI4P0VDRu
    [6]: https://play.golang.org/p/4xI4P0VDRu
    [7]: https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts
    [8]: https://pkg.go.dev/github.com/google/go-cmp@v0.5.8#section-readme
  2. StevenACoffman revised this gist Jul 26, 2024. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions StructCompareExceptOneField.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    From https://stackoverflow.com/questions/47134293/compare-structs-except-one-field-golang

    ## 1. With embedding

    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:
  3. StevenACoffman created this gist Jul 26, 2024.
    106 changes: 106 additions & 0 deletions StructCompareExceptOneField.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,106 @@
    ## 1. With embedding

    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][1].

    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.

    ## 2. Temporarily modifying the excludable fields

    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][2].

    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][3].

    ## 3. Implement your own, custom comparison

    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][4].

    **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][5]):

    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][6]):

    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)
    }


    [1]: https://play.golang.org/p/KNMfSXoj9P
    [2]: https://play.golang.org/p/3OKJ6gi_Hl
    [3]: https://play.golang.org/p/rIOBVWBOhB
    [4]: https://play.golang.org/p/LJ4cVhyGui
    [5]: https://play.golang.org/p/jRxOQyPXAM
    [6]: https://play.golang.org/p/4xI4P0VDRu