Created
March 6, 2017 07:46
-
-
Save DaidoujiChen/0eab4c7566ff4475b4eb7c77cf065226 to your computer and use it in GitHub Desktop.
Dig golang function reflect.DeepEqual when we need to check json struct is equal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"net/http" | |
"reflect" | |
"unsafe" | |
log "github.com/Sirupsen/logrus" | |
) | |
type visit struct { | |
a1 unsafe.Pointer | |
a2 unsafe.Pointer | |
typ reflect.Type | |
} | |
// the function is copy from reflect | |
func deepValueEqual(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { | |
if !v1.IsValid() || !v2.IsValid() { | |
return v1.IsValid() == v2.IsValid() | |
} | |
if v1.Type() != v2.Type() { | |
return false | |
} | |
// if depth > 10 { panic("deepValueEqual") } // for debugging | |
// We want to avoid putting more in the visited map than we need to. | |
// For any possible reference cycle that might be encountered, | |
// hard(t) needs to return true for at least one of the types in the cycle. | |
hard := func(k reflect.Kind) bool { | |
switch k { | |
case reflect.Map, reflect.Slice, reflect.Ptr, reflect.Interface: | |
return true | |
} | |
return false | |
} | |
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { | |
addr1 := unsafe.Pointer(v1.UnsafeAddr()) | |
addr2 := unsafe.Pointer(v2.UnsafeAddr()) | |
if uintptr(addr1) > uintptr(addr2) { | |
// Canonicalize order to reduce number of entries in visited. | |
// Assumes non-moving garbage collector. | |
addr1, addr2 = addr2, addr1 | |
} | |
// Short circuit if references are already seen. | |
typ := v1.Type() | |
v := visit{addr1, addr2, typ} | |
if visited[v] { | |
return true | |
} | |
// Remember for later. | |
visited[v] = true | |
} | |
switch v1.Kind() { | |
case reflect.Array: | |
for i := 0; i < v1.Len(); i++ { | |
if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) { | |
return false | |
} | |
} | |
return true | |
case reflect.Slice: | |
if v1.IsNil() != v2.IsNil() { | |
fmt.Println("content :", v1, "type :", v1.Kind(), "isNil :", v1.IsNil()) | |
fmt.Println("content :", v2, "type :", v2.Kind(), "isNil :", v2.IsNil()) | |
return false | |
} | |
if v1.Len() != v2.Len() { | |
return false | |
} | |
if v1.Pointer() == v2.Pointer() { | |
return true | |
} | |
for i := 0; i < v1.Len(); i++ { | |
if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) { | |
return false | |
} | |
} | |
return true | |
case reflect.Interface: | |
if v1.IsNil() || v2.IsNil() { | |
return v1.IsNil() == v2.IsNil() | |
} | |
return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1) | |
case reflect.Ptr: | |
if v1.Pointer() == v2.Pointer() { | |
return true | |
} | |
return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1) | |
case reflect.Struct: | |
for i, n := 0, v1.NumField(); i < n; i++ { | |
if !deepValueEqual(v1.Field(i), v2.Field(i), visited, depth+1) { | |
return false | |
} | |
} | |
return true | |
case reflect.Map: | |
if v1.IsNil() != v2.IsNil() { | |
return false | |
} | |
if v1.Len() != v2.Len() { | |
return false | |
} | |
if v1.Pointer() == v2.Pointer() { | |
return true | |
} | |
for _, k := range v1.MapKeys() { | |
val1 := v1.MapIndex(k) | |
val2 := v2.MapIndex(k) | |
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) { | |
return false | |
} | |
} | |
return true | |
case reflect.Func: | |
if v1.IsNil() && v2.IsNil() { | |
return true | |
} | |
// Can't do better than this: | |
return false | |
default: | |
// Normal equality suffices | |
return false //valueInterface(v1, false) == valueInterface(v2, false) | |
} | |
} | |
type ACLResponse struct { | |
Blacklist []string "`json:blacklist`" | |
Whitelist []string "`json:whitelist`" | |
} | |
func (aclRes ACLResponse) blacklistString() string { | |
return aclRes.toString(aclRes.Blacklist) | |
} | |
func (aclRes ACLResponse) whitelistString() string { | |
return aclRes.toString(aclRes.Whitelist) | |
} | |
func (aclRes ACLResponse) toString(list []string) string { | |
result := "[]" | |
if len(list) > 0 { | |
if listBytes, err := json.Marshal(list); err != nil { | |
log.Warningf("Failed to marshal blacklist: %v", err) | |
} else { | |
result = string(listBytes) | |
} | |
} | |
return result | |
} | |
func fetchACLFrom(url string) (ACLResponse, error) { | |
acl := ACLResponse{} | |
resp, err := http.Get(url) | |
if err != nil { | |
return acl, err | |
} | |
defer resp.Body.Close() | |
body, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return acl, err | |
} | |
err = json.Unmarshal(body, &acl) | |
if err != nil { | |
return acl, err | |
} | |
return acl, nil | |
} | |
func main() { | |
result, err := fetchACLFrom("https://s3-ap-northeast-1.amazonaws.com/daidoujiminecraft/Daidouji/man_js_test/OnlyWhiteList.json") | |
if err != nil { | |
log.Fatalf("Failed to read json file: %v", err) | |
} | |
fmt.Println("Fetch JSON : { \"whitelist\": [\"hello\", \"world\"] }") | |
checkFail := ACLResponse{Blacklist: []string{}, Whitelist: []string{"hello", "world"}} | |
fmt.Println("If we set both Blacklist and Whitelist") | |
fmt.Println("reflect.DeepEqual will return :", reflect.DeepEqual(result, checkFail)) | |
if !reflect.DeepEqual(result, checkFail) { | |
fmt.Println("deepValueEqual Result :", deepValueEqual(reflect.ValueOf(result), reflect.ValueOf(checkFail), make(map[visit]bool), 0)) | |
} | |
fmt.Println("Press any key to continue...") | |
var input string | |
fmt.Scanln(&input) | |
checkPass := ACLResponse{Whitelist: []string{"hello", "world"}} | |
fmt.Println("If we only set Whitelist") | |
fmt.Println("reflect.DeepEqual will return :", reflect.DeepEqual(result, checkPass)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment