Golang之如何(优雅的)比较两个未知结构的json
這是之前遇到的一道面試題,后來也確實在工作中實際遇到了。于是記錄一下,如何(優雅的)比較兩個未知結構的json。
假設,現在有兩個簡單的json文件。
{
"id":1,
"name":"testjson01",
"isadmin":true
}
{
"isadmin":true,
"name":"testjson01",
"id":1
}
那么,如何比較這兩個json的內容是否相同呢?
首先,最基本的方法就是利用golang的反射提供的DeepEqual()
假設我們有一個讀取json文件的函數如下:
func LoadJson(path string, dist interface{}) (err error) {
var content []byte
if content, err = ioutil.ReadFile(path); err == nil {
err = json.Unmarshal(content, dist)
}
return err
}
那么,我們可以調用該函數來讀取json文件。由于json的結構是未知的,所以我們需要聲明一個map[string]interface{}來存放對json文件的解析結果。
func main() {
var (
json1 map[string]interface{}
json2 map[string]interface{}
)
if err := service.LoadJson("./etc/json/json01.json", &json1); err != nil {
fmt.Println(err)
}
if err := service.LoadJson("./etc/json/json02.json", &json2); err != nil {
fmt.Println(err)
}
fmt.Println(reflect.DeepEqual(json1, json2))
}
這會在終端中輸出一個比較的結果:
true
如果我們只需要知道兩個json是否相同,那么這樣一段簡單的代碼就可以實現這個要求。
接下來,我們要解決“優雅的”這個定語。
大多數情況下,我們比較兩個json,不止需要知道他們是否相同。在他們結構不同的時候,我們還會很自然的關心,他們的區別在哪里。
下面就來解決這個問題。
首先,我們來分析一下json的結構。json作為一個類map的結構體,他的value可能分為3類:
1. json。json的值可能還是json。這就意味著,遇到了值為json的情況,我們需要進行嵌套的比較。另外一點需要注意的,是json結構體本身是無序的,所以比較過程中,要處理好這一點。
2. jsonArray。json的值也有可能是jsonArray。這不僅帶來了嵌套比較,還要注意,jsonArray跟json相比,它是有序的。
3. 簡單值。這里的簡單值包括字符串,實數和布爾值。簡單值只需要比較類型和值是否相同即可,也不存在嵌套的情況。
那么思路就清晰了,對于兩個json結構體json1和json2,我們首先要遍歷json1的鍵值對,檢查json2是否存在對應的鍵值對,然后根據值的類型分別進行處理。
這里,我們利用golang的反射value.(type)。需要注意的是,value.(type)只能用在switch-case結構中,當我們通過switch判斷了value的類型之后,就可以利用斷言對其進行類型轉換。
在簡單值的比較中,因為其不存在結構嵌套的情況,值不同即說明該處存在不同,這樣我們就可以用DeepEqual()來簡化比較過程。
最后再檢查json2中是否存在json1不存在的鍵值對。
這樣,比較是否相同這一目的就達到了。但是目前,這與DeepEqual()并沒有不同。所以,我們還需要把整個比較的過程記錄下來。對于相同的部分,我們記錄json的內容;對于不同的部分,我們分別記錄下兩者的區別。
type JsonDiff struct {
HasDiff bool
Result string
}
func marshal(j interface{}) string {
value, _ := json.Marshal(j)
return string(value)
}
func jsonDiffDict(json1, json2 map[string]interface{}, depth int, diff *JsonDiff) {
blank := strings.Repeat(" ", (2 * (depth - 1)))
longBlank := strings.Repeat(" ", (2 * (depth)))
diff.Result = diff.Result + "\n" + blank + "{"
for key, value := range json1 {
quotedKey := fmt.Sprintf("\"%s\"", key)
if _, ok := json2[key]; ok {
switch value.(type) {
case map[string]interface{}:
if _, ok2 := json2[key].(map[string]interface{}); !ok2 {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + ","
diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key])
} else {
diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": "
jsonDiffDict(value.(map[string]interface{}), json2[key].(map[string]interface{}), depth+1, diff)
}
case []interface{}:
diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": "
if _, ok2 := json2[key].([]interface{}); !ok2 {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + ","
diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key])
} else {
jsonDiffList(value.([]interface{}), json2[key].([]interface{}), depth+1, diff)
}
default:
if !reflect.DeepEqual(value, json2[key]) {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + ","
diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key])
} else {
diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " + marshal(value)
}
}
} else {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value)
}
diff.Result = diff.Result + ","
}
for key, value := range json2 {
if _, ok := json1[key]; !ok {
diff.HasDiff = true
diff.Result = diff.Result + "\n+" + blank + "\"" + key + "\"" + ": " + marshal(value) + ","
}
}
diff.Result = diff.Result + "\n" + blank + "}"
}
func jsonDiffList(json1, json2 []interface{}, depth int, diff *JsonDiff) {
blank := strings.Repeat(" ", (2 * (depth - 1)))
longBlank := strings.Repeat(" ", (2 * (depth)))
diff.Result = diff.Result + "\n" + blank + "["
size := len(json1)
if size > len(json2) {
size = len(json2)
}
for i := 0; i < size; i++ {
switch json1[i].(type) {
case map[string]interface{}:
if _, ok := json2[i].(map[string]interface{}); ok {
jsonDiffDict(json1[i].(map[string]interface{}), json2[i].(map[string]interface{}), depth+1, diff)
} else {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + ","
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
}
case []interface{}:
if _, ok2 := json2[i].([]interface{}); !ok2 {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + ","
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
} else {
jsonDiffList(json1[i].([]interface{}), json2[i].([]interface{}), depth+1, diff)
}
default:
if !reflect.DeepEqual(json1[i], json2[i]) {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + ","
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
} else {
diff.Result = diff.Result + "\n" + longBlank + marshal(json1[i])
}
}
diff.Result = diff.Result + ","
}
for i := size; i < len(json1); i++ {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i])
diff.Result = diff.Result + ","
}
for i := size; i < len(json2); i++ {
diff.HasDiff = true
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
diff.Result = diff.Result + ","
}
diff.Result = diff.Result + "\n" + blank + "]"
}
因為可能會出現,json很長,但是區別只有一兩行這種情況,所以我們還需要設定一個輸出范圍寬度的設定。
當寬度<0時,輸出完整的json比較結果。當寬度>=0時,將輸出區別范圍結果向上下各擴展寬度行的結果。
那么,完整代碼如下:
package service import (
"encoding/json"
"fmt"
"io/ioutil"
"reflect"
"strings"
) type JsonDiff struct {
HasDiff bool
Result string
} func JsonCompare(left, right map[string]interface{}, n int) (string, bool) {
diff := &JsonDiff{HasDiff: false, Result: ""}
jsonDiffDict(left, right, 1, diff)
if diff.HasDiff {
if n < 0 {
return diff.Result, diff.HasDiff
} else {
return processContext(diff.Result, n), diff.HasDiff
}
}
return "", diff.HasDiff
} func marshal(j interface{}) string {
value, _ := json.Marshal(j)
return string(value)
} func jsonDiffDict(json1, json2 map[string]interface{}, depth int, diff *JsonDiff) {
blank := strings.Repeat(" ", (2 * (depth - 1)))
longBlank := strings.Repeat(" ", (2 * (depth)))
diff.Result = diff.Result + "\n" + blank + "{"
for key, value := range json1 {
quotedKey := fmt.Sprintf("\"%s\"", key)
if _, ok := json2[key]; ok {
switch value.(type) {
case map[string]interface{}:
if _, ok2 := json2[key].(map[string]interface{}); !ok2 {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + ","
diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key])
} else {
diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": "
jsonDiffDict(value.(map[string]interface{}), json2[key].(map[string]interface{}), depth+1, diff)
}
case []interface{}:
diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": "
if _, ok2 := json2[key].([]interface{}); !ok2 {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + ","
diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key])
} else {
jsonDiffList(value.([]interface{}), json2[key].([]interface{}), depth+1, diff)
}
default:
if !reflect.DeepEqual(value, json2[key]) {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + ","
diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key])
} else {
diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " + marshal(value)
}
}
} else {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value)
}
diff.Result = diff.Result + ","
}
for key, value := range json2 {
if _, ok := json1[key]; !ok {
diff.HasDiff = true
diff.Result = diff.Result + "\n+" + blank + "\"" + key + "\"" + ": " + marshal(value) + ","
}
}
diff.Result = diff.Result + "\n" + blank + "}"
} func jsonDiffList(json1, json2 []interface{}, depth int, diff *JsonDiff) {
blank := strings.Repeat(" ", (2 * (depth - 1)))
longBlank := strings.Repeat(" ", (2 * (depth)))
diff.Result = diff.Result + "\n" + blank + "["
size := len(json1)
if size > len(json2) {
size = len(json2)
}
for i := 0; i < size; i++ {
switch json1[i].(type) {
case map[string]interface{}:
if _, ok := json2[i].(map[string]interface{}); ok {
jsonDiffDict(json1[i].(map[string]interface{}), json2[i].(map[string]interface{}), depth+1, diff)
} else {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + ","
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
}
case []interface{}:
if _, ok2 := json2[i].([]interface{}); !ok2 {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + ","
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
} else {
jsonDiffList(json1[i].([]interface{}), json2[i].([]interface{}), depth+1, diff)
}
default:
if !reflect.DeepEqual(json1[i], json2[i]) {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + ","
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
} else {
diff.Result = diff.Result + "\n" + longBlank + marshal(json1[i])
}
}
diff.Result = diff.Result + ","
}
for i := size; i < len(json1); i++ {
diff.HasDiff = true
diff.Result = diff.Result + "\n-" + blank + marshal(json1[i])
diff.Result = diff.Result + ","
}
for i := size; i < len(json2); i++ {
diff.HasDiff = true
diff.Result = diff.Result + "\n+" + blank + marshal(json2[i])
diff.Result = diff.Result + ","
}
diff.Result = diff.Result + "\n" + blank + "]"
} func processContext(diff string, n int) string {
index1 := strings.Index(diff, "\n-")
index2 := strings.Index(diff, "\n+")
begin := 0
end := 0
if index1 >= 0 && index2 >= 0 {
if index1 <= index2 {
begin = index1
} else {
begin = index2
}
} else if index1 >= 0 {
begin = index1
} else if index2 >= 0 {
begin = index2
}
index1 = strings.LastIndex(diff, "\n-")
index2 = strings.LastIndex(diff, "\n+")
if index1 >= 0 && index2 >= 0 {
if index1 <= index2 {
end = index2
} else {
end = index1
}
} else if index1 >= 0 {
end = index1
} else if index2 >= 0 {
end = index2
}
pre := diff[0:begin]
post := diff[end:]
i := 0
l := begin
for i < n && l >= 0 {
i++
l = strings.LastIndex(pre[0:l], "\n")
}
r := 0
j := 0
for j <= n && r >= 0 {
j++
t := strings.Index(post[r:], "\n")
if t >= 0 {
r = r + t + 1
}
}
if r < 0 {
r = len(post)
}
return pre[l+1:] + diff[begin:end] + post[0:r+1]
} func LoadJson(path string, dist interface{}) (err error) {
var content []byte
if content, err = ioutil.ReadFile(path); err == nil {
err = json.Unmarshal(content, dist)
}
return err
}
總結
以上是生活随笔為你收集整理的Golang之如何(优雅的)比较两个未知结构的json的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习Java第三天
- 下一篇: IIS配置svc(IIS8中添加WCF支