github.com/hedzr/evendeep@v0.4.8/diff/diff.go (about) 1 package diff 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "unsafe" 8 9 "github.com/hedzr/evendeep/dbglog" 10 "github.com/hedzr/evendeep/internal/natsort" 11 "github.com/hedzr/evendeep/internal/tool" 12 "github.com/hedzr/evendeep/typ" 13 ) 14 15 // New compares two value deeply and returns the Diff of them. 16 // 17 // A Diff includes added, removed, and modified records, you can 18 // PrettyPrint them for displaying. 19 // 20 // delta, equal := evendeep.DeepDiff([]int{3, 0}, []int{9, 3, 0}) 21 // t.Logf("delta: %v", delta) 22 // // added: [2] = <zero> 23 // // modified: [0] = 9 (int) (Old: 3) 24 // // modified: [1] = 3 (int) (Old: <zero>) 25 func New(lhs, rhs typ.Any, opts ...Opt) (inf Diff, equal bool) { 26 info1 := newInfo(opts...) 27 equal = info1.diff(lhs, rhs) 28 inf = info1 29 return 30 } 31 32 // Diff includes added, removed and modified records of the two values. 33 type Diff interface { 34 ForAdded(fn func(key string, val typ.Any)) 35 ForRemoved(fn func(key string, val typ.Any)) 36 ForModified(fn func(key string, val Update)) 37 38 PrettyPrint() string 39 String() string 40 } 41 42 func newInfo(opts ...Opt) *info { 43 inf := &info{ 44 added: make(map[string]typ.Any), 45 removed: make(map[string]typ.Any), 46 modified: make(map[string]Update), 47 pathTable: make(map[string]Path), 48 visited: make(map[visit]bool), 49 ignoredFields: make(map[string]bool), 50 sliceNoOrder: false, 51 stripPtr1st: false, 52 treatEmptyStructPtrAsNil: false, 53 differentTypeStructs: false, 54 compares: []Comparer{ 55 &timeComparer{}, 56 &bytesBufferComparer{}, 57 }, 58 } 59 for _, opt := range opts { 60 if opt != nil { 61 opt(inf) 62 } 63 } 64 return inf 65 } 66 67 type info struct { 68 added map[string]typ.Any 69 removed map[string]typ.Any 70 modified map[string]Update 71 pathTable map[string]Path 72 visited map[visit]bool 73 ignoredFields map[string]bool 74 sliceNoOrder bool 75 stripPtr1st bool 76 treatEmptyStructPtrAsNil bool 77 differentTypeStructs bool 78 ignoreUnmatchedFields bool 79 differentSizeArrays bool 80 compares []Comparer 81 } 82 83 // - Comparer 84 85 func (d *info) PutAdded(k string, v typ.Any) { d.added[k] = v } 86 func (d *info) PutRemoved(k string, v typ.Any) { d.removed[k] = v } 87 func (d *info) PutModified(k string, v Update) { d.modified[k] = v } 88 func (d *info) PutPath(path Path, parts ...PathPart) string { return d.mkkey(path, parts...) } 89 90 // - Stringer 91 92 func (d *info) String() string { return d.PrettyPrint() } 93 94 // - Diff 95 96 func (d *info) PrettyPrint() string { 97 var lines []string 98 if d != nil { 99 d.forMap(d.added, func(key string, val typ.Any) { 100 lines = append(lines, fmt.Sprintf("added: %s = %v\n", key, val)) 101 }) 102 for key, val := range d.modified { 103 if val.Old == nil { //nolint:gocritic // no need to switch to 'switch' clausev 104 lines = append(lines, fmt.Sprintf("modified: %s = %v (%v) (Old: nil)\n", 105 key, val.New, val.Typ)) 106 } else if val.New == nil { 107 lines = append(lines, fmt.Sprintf("modified: %s = nil (Old: %v (%v))\n", 108 key, val.Old, val.Typ)) 109 } else { 110 lines = append(lines, fmt.Sprintf("modified: %s = %v (%v) (Old: %v)\n", 111 key, val.New, val.Typ, val.Old)) 112 } 113 } 114 d.forMap(d.removed, func(key string, val typ.Any) { 115 lines = append(lines, fmt.Sprintf("removed: %s = %v\n", key, val)) 116 }) 117 } 118 119 natsort.Strings(lines) 120 return strings.Join(lines, "") 121 } 122 123 func (d *info) ForAdded(fn func(key string, val typ.Any)) { d.forMap(d.added, fn) } 124 func (d *info) ForRemoved(fn func(key string, val typ.Any)) { d.forMap(d.removed, fn) } 125 func (d *info) ForModified(fn func(key string, val Update)) { 126 for k, v := range d.modified { 127 fn(k, v) 128 } 129 } 130 131 func (d *info) forMap(m map[string]typ.Any, fn func(key string, val typ.Any)) { 132 for k, v := range m { 133 fn(k, v) 134 } 135 } 136 137 // - Cloneable 138 139 func (d *info) Clone() *info { 140 copym1 := func(m1 map[string]typ.Any) map[string]typ.Any { 141 m2 := make(map[string]typ.Any) 142 for k, v := range m1 { 143 m2[k] = v 144 } 145 return m2 146 } 147 copym2 := func(m1 map[string]Update) map[string]Update { 148 m2 := make(map[string]Update) 149 for k, v := range m1 { 150 m2[k] = v 151 } 152 return m2 153 } 154 copym3 := func(m1 map[string]Path) map[string]Path { 155 m2 := make(map[string]Path) 156 for k, v := range m1 { 157 m2[k] = v 158 } 159 return m2 160 } 161 copym4 := func(m1 map[visit]bool) map[visit]bool { 162 m2 := make(map[visit]bool) 163 for k, v := range m1 { 164 m2[k] = v 165 } 166 return m2 167 } 168 copym5 := func(m1 map[string]bool) map[string]bool { 169 m2 := make(map[string]bool) 170 for k, v := range m1 { 171 m2[k] = v 172 } 173 return m2 174 } 175 return &info{ 176 added: copym1(d.added), 177 removed: copym1(d.removed), 178 modified: copym2(d.modified), 179 pathTable: copym3(d.pathTable), 180 visited: copym4(d.visited), 181 ignoredFields: copym5(d.ignoredFields), 182 sliceNoOrder: d.sliceNoOrder, 183 } 184 } 185 186 // 187 188 func (d *info) mkkey(path Path, parts ...PathPart) (key string) { 189 dp := path.appendAndNew(parts...) 190 key = dp.String() 191 d.pathTable[key] = dp 192 return 193 } 194 195 func (d *info) diff(lhs, rhs typ.Any) bool { 196 lv, rv := reflect.ValueOf(lhs), reflect.ValueOf(rhs) 197 if d.stripPtr1st { 198 lv, rv = tool.Rdecodesimple(lv), tool.Rdecodesimple(rv) 199 } else { 200 _, lv = tool.Rskip(lv, reflect.Interface) 201 _, rv = tool.Rskip(rv, reflect.Interface) 202 } 203 var path Path 204 return d.diffv(lv, rv, path) 205 } 206 207 func (d *info) diffv(lv, rv reflect.Value, path Path) (equal bool) { 208 var processed bool 209 210 lvv, rvv := lv.IsValid(), rv.IsValid() 211 if equal, processed = d.testinvalid(lv, rv, lvv, rvv, path); processed { 212 dbglog.Log(" - Invalid object found: l = %v, r = %v, path = %v", tool.Valfmt(&lv), tool.Valfmtptr(&rv), path) 213 return 214 } 215 216 lvt, rvt := lv.Type(), rv.Type() 217 if lvt != rvt { 218 dbglog.Log(" - Unmatched type found: l = %v, r = %v, path = %v", tool.Typfmt(lvt), tool.Typfmt(rvt), path) 219 if d.differentTypeStructs && lv.Kind() == reflect.Struct && rv.Kind() == reflect.Struct { 220 return d.compareStructFields(lv, rv, path) 221 } 222 if d.differentSizeArrays && lv.Kind() == reflect.Array && rv.Kind() == reflect.Array { 223 return d.compareArrayDifferSizes(lv, rv, path) 224 } 225 d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&rv)}) 226 return 227 } 228 229 var kind = lv.Kind() 230 231 if equal, processed = d.testvisited(lv, rv, lvt, path, kind); processed { 232 return 233 } 234 235 if equal, processed = d.testnil(lv, rv, lvt, path, kind); processed { 236 return 237 } 238 239 if equal, processed = d.testcomparer(lv, rv, lvt, path); processed { 240 return 241 } 242 243 return d.diffw(lv, rv, lvt, path, kind) 244 } 245 246 func (d *info) testinvalid(lv, rv reflect.Value, lvv, rvv bool, path Path) (equal, processed bool) { 247 if !lvv && !rvv { 248 return true, true 249 } 250 251 if !lvv { 252 d.PutModified(d.mkkey(path), Update{Old: nil, New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&rv)}) 253 return false, true 254 } 255 if !rvv { 256 d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: nil, Typ: tool.Typfmtvlite(&lv)}) 257 return false, true 258 } 259 return 260 } 261 262 func (d *info) testvisited(lv, rv reflect.Value, typ1 reflect.Type, path Path, 263 kind reflect.Kind) (equal, processed bool) { 264 if lv.CanAddr() && rv.CanAddr() && 265 tool.KindIs(kind, reflect.Array, reflect.Map, reflect.Slice, reflect.Struct) { 266 addr1 := unsafe.Pointer(lv.UnsafeAddr()) 267 addr2 := unsafe.Pointer(rv.UnsafeAddr()) 268 if uintptr(addr1) > uintptr(addr2) { 269 // Canonicalize order to reduce number of entries in visited. 270 // Assumes non-moving garbage collector. 271 addr1, addr2 = addr2, addr1 272 } 273 274 // Short circuit if references are already seen. 275 v := visit{addr1, addr2, typ1} 276 if d.visited[v] { 277 return true, true 278 } 279 280 // Remember for later. 281 d.visited[v] = true 282 } 283 return 284 } 285 286 func (d *info) testnil(lv, rv reflect.Value, typ1 reflect.Type, path Path, kind reflect.Kind) (equal, processed bool) { 287 switch kind { //nolint:exhaustive //no need 288 case reflect.Map, reflect.Ptr, reflect.Func, reflect.Chan, reflect.Slice: 289 ln, rn := tool.IsNil(lv), tool.IsNil(rv) 290 if ln && rn { 291 return true, true 292 } 293 if ln || rn { 294 if kind == reflect.Slice || kind == reflect.Map { 295 // le, re := tool.IsZero(lv), tool.IsZero(rv) 296 // if equal = le == re; equal { 297 // return true, true 298 // } 299 300 // By default, the nil array are equal to an empty array 301 302 // if d.treatEmptyStructPtrAsNil { 303 if (ln) && isEmptyObject(rv) { 304 return true, true 305 } else if (rn) && isEmptyObject(lv) { 306 return true, true 307 } 308 // } 309 } else if kind == reflect.Struct && d.treatEmptyStructPtrAsNil { 310 if ln && isEmptyStruct(rv) { 311 return true, true 312 } else if rn && isEmptyStruct(lv) { 313 return true, true 314 } 315 } 316 d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&lv)}) 317 return false, true 318 } 319 } 320 return 321 } 322 323 func (d *info) testcomparer(lv, rv reflect.Value, typ1 reflect.Type, 324 path Path) (equal, processed bool) { //nolint:nonamedreturns //i do 325 var c Comparer 326 if c, processed = d.findComparer(typ1); processed { 327 equal = c.Equal(d, lv, rv, path) 328 } 329 return 330 } 331 332 func (d *info) findComparer(typ1 reflect.Type) (c Comparer, ok bool) { 333 for _, c = range d.compares { 334 if ok = c.Match(typ1); ok { 335 break 336 } 337 } 338 return 339 } 340 341 func (d *info) diffw(lv, rv reflect.Value, typ1 reflect.Type, path Path, kind reflect.Kind) (equal bool) { 342 switch kind { //nolint:exhaustive //no 343 case reflect.Array: 344 equal = d.diffArray(lv, rv, path) 345 346 case reflect.Slice: 347 if d.sliceNoOrder { 348 equal = d.diffSliceNoOrder(lv, rv, path) 349 } else { 350 equal = d.diffArray(lv, rv, path) 351 } 352 353 case reflect.Map: 354 equal = d.diffMap(lv, rv, path) 355 356 case reflect.Struct: 357 equal = d.diffStruct(lv, rv, typ1, path) 358 359 case reflect.Ptr: 360 equal = d.diffv(lv.Elem(), rv.Elem(), path) 361 362 case reflect.Interface: 363 equal = d.diffv(lv.Elem(), rv.Elem(), path) 364 365 case reflect.Chan: 366 equal = lv.Type() == rv.Type() && lv.Cap() == rv.Cap() 367 368 default: 369 a, b := lv.Interface(), rv.Interface() 370 if equal = reflect.DeepEqual(a, b); !equal { 371 d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&lv), New: tool.Valfmt(&rv), Typ: tool.Typfmtvlite(&lv)}) 372 } 373 } 374 375 return 376 } 377 378 func (d *info) diffArray(lv, rv reflect.Value, path Path) (equal bool) { 379 ll, rl := lv.Len(), rv.Len() 380 equal = true 381 for i := 0; i < tool.MinInt(ll, rl); i++ { 382 localPath := path.appendAndNew(sliceIndex(i)) 383 aI, bI := lv.Index(i), rv.Index(i) 384 if eq := d.diffv(aI, bI, localPath); !eq { 385 dbglog.Log(" diffArray: [%d] not equal %v - %v", i, tool.Valfmt(&aI), tool.Valfmt(&bI)) 386 equal = false 387 } 388 } 389 if ll > rl { 390 for i := rl; i < ll; i++ { 391 v := lv.Index(i) 392 if d.differentSizeArrays && tool.IsZero(v) { 393 continue 394 } 395 localPath := path.appendAndNew(sliceIndex(i)) 396 d.PutRemoved(d.mkkey(localPath), tool.Valfmt(&v)) 397 equal = false 398 } 399 } else if ll < rl { 400 for i := ll; i < rl; i++ { 401 v := rv.Index(i) 402 if d.differentSizeArrays && tool.IsZero(v) { 403 continue 404 } 405 localPath := path.appendAndNew(sliceIndex(i)) 406 d.PutAdded(d.mkkey(localPath), tool.Valfmt(&v)) 407 equal = false 408 } 409 } 410 return 411 } 412 413 func (d *info) diffSliceNoOrder(lv, rv reflect.Value, path Path) (equal bool) { 414 ll, rl := lv.Len(), rv.Len() 415 equal = true 416 m := make(map[int]bool) 417 for i := 0; i < tool.MinInt(ll, rl); i++ { 418 localPath := path.appendAndNew(sliceIndex(i)) 419 lvit := lv.Index(i) 420 var eq bool 421 for j := 0; j < rl; j++ { 422 if eq = d.Clone().diffv(lvit, rv.Index(j), localPath); eq { 423 m[j] = true 424 break 425 } 426 } 427 if !eq { 428 d.PutRemoved(d.mkkey(localPath), tool.Valfmt(&lvit)) 429 equal = false 430 } 431 } 432 for i := 0; i < rl; i++ { 433 localPath := path.appendAndNew(sliceIndex(i)) 434 if _, ok := m[i]; ok { 435 continue 436 } 437 rvit := rv.Index(i) 438 d.PutAdded(d.mkkey(localPath), tool.Valfmt(&rvit)) 439 equal = false 440 } 441 return 442 } 443 444 func (d *info) diffMap(lv, rv reflect.Value, path Path) (equal bool) { 445 equal = true 446 for _, key := range lv.MapKeys() { 447 aI, bI := lv.MapIndex(key), rv.MapIndex(key) 448 localPath := path.appendAndNew(mapKey{key.Interface()}) 449 if !bI.IsValid() { 450 d.PutRemoved(d.mkkey(localPath), tool.Valfmt(&aI)) 451 equal = false 452 } else if eq := d.diffv(aI, bI, localPath); !eq { 453 equal = false 454 } 455 } 456 for _, key := range rv.MapKeys() { 457 aI := lv.MapIndex(key) 458 if !aI.IsValid() { 459 bI := rv.MapIndex(key) 460 localPath := path.appendAndNew(mapKey{key.Interface()}) 461 d.PutAdded(d.mkkey(localPath), tool.Valfmt(&bI)) 462 equal = false 463 } 464 } 465 return 466 } 467 468 func (d *info) diffStruct(lv, rv reflect.Value, typ1 reflect.Type, path Path) (equal bool) { 469 equal = true 470 for i := 0; i < typ1.NumField(); i++ { 471 index := []int{i} 472 field := typ1.FieldByIndex(index) 473 if vk := field.Tag.Get("diff"); vk == "ignore" || vk == "-" { // skip fields marked to be ignored 474 continue 475 } 476 if _, skip := d.ignoredFields[field.Name]; skip { 477 continue 478 } 479 localPath := path.appendAndNew(structField(field.Name)) 480 aI := tool.UnsafeReflectValue(lv.FieldByIndex(index)) 481 bI := tool.UnsafeReflectValue(rv.FieldByIndex(index)) 482 if d.treatEmptyStructPtrAsNil && field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct { 483 ln, rn := tool.IsNil(aI), tool.IsNil(bI) 484 var eq bool 485 if eq = ln && rn; !equal { 486 if eq = ln && isEmptyStruct(bI.Elem()); !eq { 487 eq = rn && isEmptyStruct(aI.Elem()) 488 } 489 } 490 if !eq { 491 equal = false 492 d.PutModified(d.mkkey(path), Update{Old: tool.Valfmt(&aI), New: tool.Valfmt(&bI), Typ: tool.Typfmtvlite(&aI)}) 493 } 494 continue 495 } 496 if eq := d.diffv(aI, bI, localPath); !eq { 497 equal = false 498 } 499 } 500 return 501 } 502 503 func (d *info) compareArrayDifferSizes(lv, rv reflect.Value, path Path) (equal bool) { 504 return d.diffArray(lv, rv, path) 505 } 506 507 func (d *info) compareStructFields(lv, rv reflect.Value, path Path) (equal bool) { 508 for i, lt, rt := 0, lv.Type(), rv.Type(); i < lv.NumField(); i++ { 509 // fldval := lv.Field(i) 510 fldtyp := lt.Field(i) 511 fldname := fldtyp.Name 512 if _, ok := rt.FieldByName(fldname); ok { 513 l := lv.Field(i) 514 r := rv.FieldByName(fldname) 515 localPath := path.appendAndNew(structField(fldname)) 516 equal = d.diffv(l, r, localPath) 517 if !equal { 518 if equal = !l.IsValid() && !r.IsValid(); !equal { 519 return 520 } // if both l and r are invalid, assumes its equivalence 521 } 522 } else if !d.ignoreUnmatchedFields { 523 return false 524 } 525 } 526 return 527 }