google / go-cmp

Package for comparing Go values in tests
BSD 3-Clause "New" or "Revised" License
4.17k stars 209 forks source link

How to ignore specific field by name? #336

Closed criscola closed 1 year ago

criscola commented 1 year ago

Hello, I hope you can help me. I would like to know if it's possible to ignore a specific field during comparison. I had a look at this https://github.com/google/go-cmp/issues/156 and tried to modify it a bit to make it work for my use case, but I don't understand how to do it. For more context: my objective is to compare two JSON strings, ignoring a specific field by name present in various levels of the hierarchy. Thank you!

Test samples:

{
"name": "foo",
"archived": false,
"birth_date": "2016-05-01T00:00:00Z",
"edges": {
  "firstEdge": {
    "id": 1438,
    "edges": {
      "arrayOfStuff": [
        {
          "id": 98,
          "create_time": "2023-08-22T22:32:31.681874237+02:00",
          "update_time": "2023-08-22T22:32:31.681874317+02:00",
          "edges": {
            "yetAnotherEdge": {
              "id": 98,
              "some_date": "2020-11-02T00:00:00Z",
              "create_time": "2023-08-22T22:32:31.680396095+02:00",
              "update_time": "2023-08-22T22:32:31.680396165+02:00"
            }
          }
        }]
      }
    }
  }
}

compared to this (only create and update times are changed):

{
"name": "foo",
"archived": false,
"birth_date": "2016-05-01T00:00:00Z",
"edges": {
  "firstEdge": {
    "id": 1438,
    "edges": {
      "arrayOfStuff": [
        {
          "id": 98,
          "create_time": "1999-08-22T22:32:31.681874237+02:00",
          "update_time": "1999-08-22T22:32:31.681874317+02:00",
          "edges": {
            "yetAnotherEdge": {
              "id": 98,
              "some_date": "1999-11-02T00:00:00Z",
              "create_time": "1999-08-22T22:32:31.680396095+02:00",
              "update_time": "1999-08-22T22:32:31.680396165+02:00"
            }
          }
        }]
      }
    }
  }
}

should return a match. I know that I can use some trick like reflection to zero-out the fields, or unmarshal into a struct omitting the values, but if I can implement what I found with go-cmp it would definitely lead to more clear, concise and maintenable code.

criscola commented 1 year ago

Turns out, it is pretty simple! Here FilterPath is necessary because FilterValues is used to ignore based on values, for example to compare float values etc. but in my case I needed to ignore a path which includes the left-hand side of the "map entry" in the predicate. Simple get all the leaves with p.Last() and you're set:

opt := cmp.FilterPath(func(p cmp.Path) bool {
    vx := p.Last().String()

    if vx == `["create_time"]` || vx == `["update_time"]` {
        return true
    }
    return false
}, cmp.Ignore())

note also that if you know your struct in advance, you can use IgnoreFields as in the docs (in my case, I don't know, hence I had to use the jsonTransformer as in #156).