How to compare if two structs, slices or maps are equal?
Asked Answered
P

7

220

I want to check if two structs, slices and maps are equal.

But I'm running into problems with the following code. See my comments at the relevant lines.

package main

import (
    "fmt"
    "reflect"
)

type T struct {
    X int
    Y string
    Z []int
    M map[string]int
}

func main() {
    t1 := T{
        X: 1,
        Y: "lei",
        Z: []int{1, 2, 3},
        M: map[string]int{
            "a": 1,
            "b": 2,
        },
    }

    t2 := T{
        X: 1,
        Y: "lei",
        Z: []int{1, 2, 3},
        M: map[string]int{
            "a": 1,
            "b": 2,
        },
    }

    fmt.Println(t2 == t1)
    //error - invalid operation: t2 == t1 (struct containing []int cannot be compared)

    fmt.Println(reflect.ValueOf(t2) == reflect.ValueOf(t1))
    //false
    fmt.Println(reflect.TypeOf(t2) == reflect.TypeOf(t1))
    //true

    //Update: slice or map
    a1 := []int{1, 2, 3, 4}
    a2 := []int{1, 2, 3, 4}

    fmt.Println(a1 == a2)
    //invalid operation: a1 == a2 (slice can only be compared to nil)

    m1 := map[string]int{
        "a": 1,
        "b": 2,
    }
    m2 := map[string]int{
        "a": 1,
        "b": 2,
    }
    fmt.Println(m1 == m2)
    // m1 == m2 (map can only be compared to nil)
}

http://play.golang.org/p/AZIzW2WunI

Piraeus answered 2/7, 2014 at 14:41 Comment(1)
COnsider also 'invalid operation: t2 == t1 (struct containing map[string]int cannot be compared)', this happens if the struct has no int[] within his definitionMagnifico
E
228

You can use reflect.DeepEqual, or you can implement your own function (which performance wise would be better than using reflection):

http://play.golang.org/p/CPdfsYGNy_

m1 := map[string]int{   
    "a":1,
    "b":2,
}
m2 := map[string]int{   
    "a":1,
    "b":2,
}
fmt.Println(reflect.DeepEqual(m1, m2))
Elfin answered 2/7, 2014 at 14:53 Comment(2)
Is it possible to ignore some field like the id, such as with cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID"))?Hydrated
Rather than ignore fields, separate the ID from the data to be compared (via embedded struct); with that you can compare with ignoring the ID.. Example: go.dev/play/p/HvWunmktpkmElinoreeliot
R
140

reflect.DeepEqual is often incorrectly used to compare two like structs, as in your question.

cmp.Equal is a better tool for comparing structs.

To see why reflection is ill-advised, let's look at the documentation:

Struct values are deeply equal if their corresponding fields, both exported and unexported, are deeply equal.

....

numbers, bools, strings, and channels - are deeply equal if they are equal using Go's == operator.

If we compare two time.Time values of the same UTC time, t1 == t2 will be false if their metadata timezone is different.

go-cmp looks for the Equal() method and uses that to correctly compare times.

Example:

m1 := map[string]int{
    "a": 1,
    "b": 2,
}
m2 := map[string]int{
    "a": 1,
    "b": 2,
}
fmt.Println(cmp.Equal(m1, m2)) // will result in true

Important Note:

Be careful when using cmp.Equal as it may lead to a panic condition

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. Its propensity towards panicking means that its unsuitable for production environments where a spurious panic may be fatal.

Rosas answered 20/7, 2017 at 18:24 Comment(4)
Yes exactly! When writing tests, it's very important to use go-cmp and not reflect.Gallicism
According to the cmp documentation, using cmp is advised only when writing tests, because it may panic if objects aren't comparable.Kinship
github.com/stretchr/testify/require usesreflect.DeepEqual when comparing two structs, thus failing when two private fields are unequal.Monda
Just a note, the time package doc says not to do t1 == t2. "In general, prefer t.Equal(u) to t == u" That's what t.Equal is there for. See pkg.go.dev/time#Time and pkg.go.dev/time#Time.EqualNerval
S
32

Here's how you'd roll your own function http://play.golang.org/p/Qgw7XuLNhb

func compare(a, b *T) bool {
  if a == b {
    return true
  }
  if a.X != b.X || a.Y != b.Y {
    return false
  }
  if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) {
    return false
  }
  for i, v := range a.Z {
    if b.Z[i] != v {
      return false
    }
  }
  for k, v := range a.M {
    if b.M[k] != v {
      return false
    }
  }
  return true
}

Update: Go 1.18

import (
    "golang.org/x/exp/maps"
    "golang.org/x/exp/slices"
)

func compare(a, b *T) bool {
    if a == b {
        return true
    }
    if a.X != b.X {
        return false
    }
    if a.Y != b.Y {
        return false
    }
    if !slices.Equal(a.Z, b.Z) {
        return false
    }
    return maps.Equal(a.M, b.M)
}

Update: Go 1.21

import (
    "maps"
    "slices"
}

func (t *T) Equal(t2 *T) bool {
    if t == t2 {
        return true
    }
    return t.X == t2.T &&
        t.Y == t2.Y &&
        slices.Equal(t.Z, t2.Z) &&
        maps.Equal(t.M, t2.M)
}
Segmental answered 2/7, 2014 at 15:0 Comment(5)
I'd recommend adding if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, because one of them could have extra fields.Elfin
All the structural information is known at compile time. It's a shame the compiler can't do this heavy lifting in some way.Mannerism
@Mannerism there's simply no comparison defined for slices. This is how the language designers wanted it to be. It's not as simple to define as, say, comparison of simple integers. Are slices equal if they contain same elements in the same order? But what if their capacities differ? Etc.Deter
if &a == &b { return true } This will never evaluate to true if the parameters to compare are being passed by value.Mckay
It looks like you changed the first statement to if a == b ... which doesn't compile (hence the question from OP). I suppose you wanted to follow up on Sean's note. You'd have to change the T into *T then that first if() would compile (and work as expected).Assumed
A
18

If you intend to use it in tests, since July 2017 you can use cmp.Equal with cmpopts.IgnoreFields option.

func TestPerson(t *testing.T) {
    type person struct {
        ID   int
        Name string
    }

    p1 := person{ID: 1, Name: "john doe"}
    p2 := person{ID: 2, Name: "john doe"}
    println(cmp.Equal(p1, p2))
    println(cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID")))

    // Prints:
    // false
    // true
}
Arose answered 25/3, 2020 at 10:25 Comment(2)
Note that the cmp package is not meant for production code, as it panics if it cannot compare the values. It is only intended for tests. See the moderator annotation and comments in this answer: https://mcmap.net/q/118502/-how-to-compare-if-two-structs-slices-or-maps-are-equal.Ballata
Good find, let me amend the answer.Arose
O
11

If you're comparing them in unit test, a handy alternative is EqualValues function in testify.

Orbadiah answered 14/10, 2019 at 18:21 Comment(0)
M
6

Go 1.21

Same as below, but maps.Equal is now in the standard library, you can just use that instead of importing experimental packages or rolling your own.

Go 1.18 ~ 1.20

This proposal (https://github.com/golang/go/issues/47649) that is part of the future implementation of Go generics introduces a new function to compare two maps, maps.Equal:

// Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==.
func Equal[M1, M2 constraints.Map[K, V], K, V comparable](m1 M1, m2 M2) bool

Example use

strMapX := map[string]int{
    "one": 1,
    "two": 2,
}
strMapY := map[string]int{
    "one": 1,
    "two": 2,
}

equal := maps.Equal(strMapX, strMapY)
// equal is true

maps package is found in golang.org/x/exp/maps. This is experimental and outside of Go compatibility guarantee. They aim to move it into the std lib in Go 1.19

You can see it working in gotip playground https://gotipplay.golang.org/p/M0T6bCm1_3m

Machutte answered 8/11, 2021 at 21:0 Comment(0)
W
4

If you want to compare simple one-level structs, the best and simple method is the if statement.

Like this if s1 == s2

Here is a simple example:

type User struct { 
    name      string 
    email           string
} 

func main() {
    u1 := User{
            name: "Iron Man", 
            email: "[email protected]",
    }
    u2 := User{
            name: "Iron Man", 
            email: "[email protected]",
    }
        // Comparing 2 structs
        if u1 == u2 {
            fmt.Println("u1 is equal to u2")
        } else {
            fmt.Println("u1 is not equal to u2")
        }
}

Result: u1 is equal to u2

You can play with this here.

Wyck answered 16/3, 2021 at 12:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.