google/go-cmp

Path.String() only returns a path in a Struct

Josemaralves opened this issue · 2 comments

There is any reason why Path.String() only checks for a StructField while building the path?
This check is documented in the docs but it limits its utilization in maps of interface{} and can be improved with a little effort.

Right now it looks like this:

func (pa Path) String() string {
	var ss []string
	for _, s := range pa {
		if _, ok := s.(StructField); ok {
			ss = append(ss, s.String())
		}
	}
	return strings.TrimPrefix(strings.Join(ss, ""), ".")
}

the fix looks like:

func (pa Path) String() string {
	var ss []string
	for _, s := range pa {
		if _, ok := s.(StructField); ok {
			ss = append(ss, s.String())
		} else if index, ok := s.(MapIndex); ok {
			ss = append(ss, "."+index.Key().String())
		}
	}
	return strings.TrimPrefix(strings.Join(ss, ""), ".")
}

and a test case:

func Test(t *testing.T) {
	a := map[string]interface{}{
		"a": map[string]interface{}{
			"b": "bvalue",
			"c": "cvalue",
		},
		"d": "dvalue",
	}

	b := map[string]interface{}{
		"a": map[string]interface{}{
			"b": "evalue",
			"c": "cvalue",
		},
		"d": "dvalue",
	}

	diff := Diff(a, b, FilterPath(func(path Path) bool {
		return path.String() == "a.b"
	}, Ignore()))

	fmt.Println(diff)
}
dsnet commented

It was possibly a mistake to have Path.String only print the struct field accesses, but that behavior can't be changed since some tests already depend on that as a way to implement filters. For example, they'll do something like strings.Contains(p.String(), "Foo.Bar") to deliberately ignore any slice or map accesses between Foo and Bar.

If you want a complete print of the path, then use Path.GoString, which was added later.

dsnet commented

Closing as working as intended. The output of Path.String isn't going to change and Path.GoString has what is requested.