feat(Go-StudyExample):2020/12/31:新增【Go每日一库】go-cmp结构体比较工具
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/30 14:43
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//测试指向同一个结构体的对象
|
||||||
|
func TestStruct(t *testing.T) {
|
||||||
|
u1 := User{Name: "dj", Age: 18}
|
||||||
|
u2 := User{Name: "dj", Age: 18}
|
||||||
|
|
||||||
|
fmt.Println("u1 == u2?", u1 == u2)
|
||||||
|
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
|
||||||
|
|
||||||
|
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
|
||||||
|
u1.Contact = c1
|
||||||
|
u2.Contact = c1
|
||||||
|
fmt.Println("u1 == u2 with same pointer?", u1 == u2)
|
||||||
|
fmt.Println("u1 equals u2 with same pointer?", cmp.Equal(u1, u2))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试指向不同结构体,但是结构体值一致的对象
|
||||||
|
func TestPoint(t *testing.T) {
|
||||||
|
u1 := User{Name: "dj", Age: 18}
|
||||||
|
u2 := User{Name: "dj", Age: 18}
|
||||||
|
|
||||||
|
fmt.Println("u1 == u2?", u1 == u2)
|
||||||
|
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
|
||||||
|
|
||||||
|
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
c2 := &Contact{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
|
||||||
|
u1.Contact = c1
|
||||||
|
u2.Contact = c2
|
||||||
|
fmt.Println("u1 == u2 with different pointer?", u1 == u2)
|
||||||
|
fmt.Println("u1 equals u2 with different pointer?", cmp.Equal(u1, u2))
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/30 14:03
|
||||||
|
* @Description:输出两个对象之间的差异
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Contact struct {
|
||||||
|
Phone string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Contact *Contact
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiff(t *testing.T) {
|
||||||
|
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
c2 := &Contact{Phone: "123456879", Email: "dj2@example.com"}
|
||||||
|
u1 := User{Name: "dj", Age: 18, Contact: c1}
|
||||||
|
u2 := User{Name: "dj2", Age: 18, Contact: c2}
|
||||||
|
|
||||||
|
fmt.Println(cmp.Diff(u1, u2))
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/30 14:57
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetAddr1 struct {
|
||||||
|
IP string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareNetAddr(a, b NetAddr1) bool {
|
||||||
|
if a.Port != b.Port {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.IP != b.IP {
|
||||||
|
if a.IP == "127.0.0.1" && b.IP == "localhost" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.IP == "localhost" && b.IP == "127.0.0.1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//自定义比较器:
|
||||||
|
// 这种方式与上面介绍的自定义Equal()方法有些类似,但更灵活。
|
||||||
|
// 有时,我们要自定义比较操作的类型定义在第三方包中,这样就无法给它定义Equal方法。
|
||||||
|
// 这时,我们就可以采用自定义Comparer的方式。
|
||||||
|
func TestDiyCompareMethod(t *testing.T) {
|
||||||
|
a1 := NetAddr1{"127.0.0.1", 5000}
|
||||||
|
a2 := NetAddr1{"localhost", 5000}
|
||||||
|
|
||||||
|
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2))
|
||||||
|
fmt.Println("a1 equals a2 with comparer?", cmp.Equal(a1, a2, cmp.Comparer(compareNetAddr)))
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/30 14:49
|
||||||
|
* @Description:自定义Equal方法...
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetAddr struct {
|
||||||
|
IP string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a NetAddr) Equal(b NetAddr) bool {
|
||||||
|
if a.Port != b.Port {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.IP != b.IP {
|
||||||
|
if a.IP == "127.0.0.1" && b.IP == "localhost" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.IP == "localhost" && b.IP == "127.0.0.1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiyEqualMethod(t *testing.T) {
|
||||||
|
a1 := NetAddr{"127.0.0.1", 5000}
|
||||||
|
a2 := NetAddr{"localhost", 5000}
|
||||||
|
a3 := NetAddr{"192.168.1.1", 5000}
|
||||||
|
|
||||||
|
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2))
|
||||||
|
fmt.Println("a1 equals a3?", cmp.Equal(a1, a3))
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/30 15:11
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FloatPair struct {
|
||||||
|
X float64
|
||||||
|
Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicFloat(t *testing.T) {
|
||||||
|
//特殊的浮点数NaN(Not a Number),它与任何浮点数都不等,包括它自己
|
||||||
|
p1 := FloatPair{X: math.NaN()}
|
||||||
|
p2 := FloatPair{X: math.NaN()}
|
||||||
|
fmt.Println("p1 equals p2?", cmp.Equal(p1, p2))
|
||||||
|
|
||||||
|
//受限于变量的存储空间,就会存在误差
|
||||||
|
f1 := 0.1
|
||||||
|
f2 := 0.2
|
||||||
|
f3 := 0.3
|
||||||
|
p3 := FloatPair{X: f1 + f2}
|
||||||
|
p4 := FloatPair{X: f3}
|
||||||
|
fmt.Println("p3 equals p4?", cmp.Equal(p3, p4))
|
||||||
|
|
||||||
|
//Go 语言中这些字面量的运算直接是在编译器完成的,所以同样的值虽然受限于变量的存储空间,但是结果是一样的.
|
||||||
|
p5 := FloatPair{X: 0.1 + 0.2}
|
||||||
|
p6 := FloatPair{X: 0.3}
|
||||||
|
fmt.Println("p5 equals p6?", cmp.Equal(p5, p6))
|
||||||
|
}
|
||||||
|
|
||||||
|
//通过equal方法测试NaN返回结果一致
|
||||||
|
func TestNanEqual(t *testing.T) {
|
||||||
|
p1 := FloatPair{X: math.NaN()}
|
||||||
|
p2 := FloatPair{X: math.NaN()}
|
||||||
|
fmt.Println("p1 equals p2?", cmp.Equal(p1, p2, cmpopts.EquateNaNs()))
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试误差在一定的范围内 具体表示是:|x-y| ≤ max(fraction*min(|x|, |y|), margin)
|
||||||
|
func TestCountError(t *testing.T) {
|
||||||
|
f1 := 0.1
|
||||||
|
f2 := 0.2
|
||||||
|
f3 := 0.3
|
||||||
|
p3 := FloatPair{X: f1 + f2}
|
||||||
|
p4 := FloatPair{X: f3}
|
||||||
|
fmt.Println(fmt.Sprintf("实际存储的值分别是:\n %v \n %v", p3.X, p4.X))
|
||||||
|
fmt.Println("p3 equals p4?", cmp.Equal(p3, p4, cmpopts.EquateApprox(0.1, 0.001)))
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/30 15:21
|
||||||
|
* @Description:测试未导出字段
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//自定义是否比较未导出字段,这个方法和上面的很类似,都是必须制定相应的结构体
|
||||||
|
func TestDiyExportMethod(t *testing.T) {
|
||||||
|
u1 := User2{"dj", 18, Address{}}
|
||||||
|
u2 := User2{"dj", 18, Address{}}
|
||||||
|
|
||||||
|
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Exporter(allowUnExportedInType)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试只忽略当前对象的未导出属性
|
||||||
|
func TestExportOne(t *testing.T) {
|
||||||
|
c1 := Contactx{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
c2 := Contactx{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
|
||||||
|
u1 := Userx{"dj", 18, c1}
|
||||||
|
u2 := Userx{"dj", 18, c2}
|
||||||
|
|
||||||
|
//i think 通过 IgnoreUnexported 这个属性可以实现忽略具体类型的未导出字段,但是好像没办法忽略 【指针类型】 的未导出字段
|
||||||
|
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmpopts.IgnoreUnexported(Userx{})))
|
||||||
|
}
|
||||||
|
|
||||||
|
//结构体中有未导出字段的时候,通过Equal进行比较的时候,会报错,除非设置忽略
|
||||||
|
func TestExportError(t *testing.T) {
|
||||||
|
c1 := &Contact1{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
c2 := &Contact1{Phone: "123456789", Email: "dj@example.com"}
|
||||||
|
|
||||||
|
u1 := User1{"dj", 18, c1}
|
||||||
|
u2 := User1{"dj", 18, c2}
|
||||||
|
|
||||||
|
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
|
||||||
|
}
|
||||||
|
|
||||||
|
//使用 【IgnoreUnexported】,但是在结构体下层还有其他非导出的字段也会报错,这个字段没办法深入到里面
|
||||||
|
func TestExportError2(t *testing.T) {
|
||||||
|
u11 := User2{"dj", 18, Address{}}
|
||||||
|
u22 := User2{"dj", 18, Address{}}
|
||||||
|
|
||||||
|
fmt.Println("u1 equals u2?", cmp.Equal(u11, u22, cmpopts.IgnoreUnexported(User2{})))
|
||||||
|
}
|
||||||
|
|
||||||
|
func allowUnExportedInType(t reflect.Type) bool {
|
||||||
|
if t.Name() == "Address" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Contactx struct {
|
||||||
|
Phone string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Userx struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
contact Contactx //注意这边是结构体
|
||||||
|
}
|
||||||
|
|
||||||
|
type Contact1 struct {
|
||||||
|
Phone string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User1 struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
contact *Contact1
|
||||||
|
}
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
Province string
|
||||||
|
city string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User2 struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Address Address
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/30 14:08
|
||||||
|
* @Description:map和切片的比较方法
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNilEqualEmpty(t *testing.T) {
|
||||||
|
var s1 []int
|
||||||
|
var s2 = make([]int, 0)
|
||||||
|
|
||||||
|
var m1 map[int]int
|
||||||
|
var m2 = make(map[int]int)
|
||||||
|
|
||||||
|
//Go原生的" == "符号是没办法比较 切片和map的.
|
||||||
|
//fmt.Println(s1 == s2)
|
||||||
|
//fmt.Println(m1 == m2)
|
||||||
|
|
||||||
|
//这边可以通过Equal来比较 切片和map
|
||||||
|
fmt.Println("s1 equals s2?", cmp.Equal(s1, s2))
|
||||||
|
fmt.Println("m1 equals m2?", cmp.Equal(m1, m2))
|
||||||
|
|
||||||
|
//空的切片和nil如果要比较相等,需要加上对应的参数
|
||||||
|
fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.EquateEmpty()))
|
||||||
|
fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.EquateEmpty()))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//比较无序的切片,和本来就没有顺序的map
|
||||||
|
func TestSliceOutOfOrder(t *testing.T) {
|
||||||
|
s1 := []int{1, 2, 3, 4}
|
||||||
|
s2 := []int{4, 3, 2, 1}
|
||||||
|
fmt.Println("s1 equals s2?", cmp.Equal(s1, s2))
|
||||||
|
fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.SortSlices(func(i, j int) bool { return i < j })))
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
m1 := map[int]int{1: 10, 2: 20, 3: 30}
|
||||||
|
m2 := map[int]int{1: 10, 2: 20, 3: 30}
|
||||||
|
fmt.Println("m1 equals m2?", cmp.Equal(m1, m2))
|
||||||
|
fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.SortMaps(func(i, j int) bool { return i < j })))
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* @Author : huangzj
|
||||||
|
* @Time : 2020/12/31 14:33
|
||||||
|
* @Description: 比较的时候转换对应的属性,改变结构体对比的规则
|
||||||
|
*/
|
||||||
|
|
||||||
|
package go_cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserO struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
func omitAge(u UserO) string {
|
||||||
|
return u.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserO2 struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Email string
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func omitAge2(u UserO2) UserO2 {
|
||||||
|
return UserO2{u.Name, 0, u.Email, u.Address}
|
||||||
|
}
|
||||||
|
|
||||||
|
//对某个字段忽略比较,也就是返回字段默认值
|
||||||
|
func TestOmit(t *testing.T) {
|
||||||
|
u1 := UserO{Name: "dj", Age: 18}
|
||||||
|
u2 := UserO{Name: "dj", Age: 28}
|
||||||
|
|
||||||
|
//i think 这边omitAge只返回name属性应该是值比较UserO的name属性即可
|
||||||
|
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Transformer("omitAge", omitAge)))
|
||||||
|
|
||||||
|
u3 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"}
|
||||||
|
u4 := UserO2{Name: "dj", Age: 28, Email: "dj@example.com"}
|
||||||
|
|
||||||
|
u5 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"}
|
||||||
|
u6 := UserO2{Name: "dj1", Age: 28, Email: "dj@example.com"}
|
||||||
|
|
||||||
|
//i think 这边的omitAge2返回修改之后的UserO2,只把age设置为0,其他属性不变,说明还是会参与到比较
|
||||||
|
fmt.Println("u3 equals u4?", cmp.Equal(u3, u4, cmp.Transformer("omitAge", omitAge2)))
|
||||||
|
|
||||||
|
fmt.Println("u5 equals u5?", cmp.Equal(u5, u6, cmp.Transformer("omitAge", omitAge2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetAddrO struct {
|
||||||
|
IP string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformLocalhost(a NetAddrO) NetAddrO {
|
||||||
|
if a.IP == "localhost" {
|
||||||
|
return NetAddrO{IP: "127.0.0.1", Port: a.Port}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
//对结构体进行对比的时候,修改某字段的值
|
||||||
|
func TestTransformValue(t *testing.T) {
|
||||||
|
a1 := NetAddrO{"127.0.0.1", 5000}
|
||||||
|
a2 := NetAddrO{"localhost", 5000}
|
||||||
|
|
||||||
|
//这边是把NetAddr对应的IP进行转换,localhost == 127.0.0.1 所以直接进行转换,比较的结果就会一致
|
||||||
|
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2, cmp.Transformer("localhost", transformLocalhost)))
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
获取包命令:go get -u github.com/google/go-cmp
|
||||||
|
|
||||||
|
参考地址:https://segmentfault.com/a/1190000021997004
|
||||||
|
|
||||||
|
参考地址:https://github.com/google/go-cmp
|
||||||
|
|
||||||
|
参考地址:https://github.com/darjun/go-daily-lib
|
||||||
@@ -43,6 +43,8 @@ json与struct之间转换处理工具使用示例
|
|||||||
|
|
||||||
2020/12/29:新增【Go每日一库】emial发送邮件
|
2020/12/29:新增【Go每日一库】emial发送邮件
|
||||||
|
|
||||||
|
2020/12/31:新增【Go每日一库】go-cmp结构体比较工具
|
||||||
|
|
||||||
# mod vendor模式加载包
|
# mod vendor模式加载包
|
||||||
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到
|
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user