Golang之如何(优雅的)比较两个未知结构的json
本文内容纲要:
这是之前遇到的一道面试题,后来也确实在工作中实际遇到了。于是记录一下,如何(优雅的)比较两个未知结构的json。
假设,现在有两个简单的json文件。
{
"id":1,
"name":"testjson01",
"isadmin":true
}
{
"isadmin":true,
"name":"testjson01",
"id":1
}
那么,如何比较这两个json的内容是否相同呢?
首先,最基本的方法就是利用golang的反射提供的DeepEqual()
假设我们有一个读取json文件的函数如下:
funcLoadJson(pathstring,distinterface{})(errerror){
varcontent[]byte
ifcontent,err=ioutil.ReadFile(path);err==nil{
err=json.Unmarshal(content,dist)
}
returnerr
}
那么,我们可以调用该函数来读取json文件。由于json的结构是未知的,所以我们需要声明一个map[string]interface{}来存放对json文件的解析结果。
funcmain(){
var(
json1map[string]interface{}
json2map[string]interface{}
)
iferr:=service.LoadJson("./etc/json/json01.json",&json1);err!=nil{
fmt.Println(err)
}
iferr:=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类:
-
json。json的值可能还是json。这就意味着,遇到了值为json的情况,我们需要进行嵌套的比较。另外一点需要注意的,是json结构体本身是无序的,所以比较过程中,要处理好这一点。
-
jsonArray。json的值也有可能是jsonArray。这不仅带来了嵌套比较,还要注意,jsonArray跟json相比,它是有序的。
-
简单值。这里的简单值包括字符串,实数和布尔值。简单值只需要比较类型和值是否相同即可,也不存在嵌套的情况。
那么思路就清晰了,对于两个json结构体json1和json2,我们首先要遍历json1的键值对,检查json2是否存在对应的键值对,然后根据值的类型分别进行处理。
这里,我们利用golang的反射value.(type)。需要注意的是,value.(type)只能用在switch-case结构中,当我们通过switch判断了value的类型之后,就可以利用断言对其进行类型转换。
在简单值的比较中,因为其不存在结构嵌套的情况,值不同即说明该处存在不同,这样我们就可以用DeepEqual()来简化比较过程。
最后再检查json2中是否存在json1不存在的键值对。
这样,比较是否相同这一目的就达到了。但是目前,这与DeepEqual()并没有不同。所以,我们还需要把整个比较的过程记录下来。对于相同的部分,我们记录json的内容;对于不同的部分,我们分别记录下两者的区别。
typeJsonDiffstruct{
HasDiffbool
Resultstring
}
funcmarshal(jinterface{})string{
value,_:=json.Marshal(j)
returnstring(value)
}
funcjsonDiffDict(json1,json2map[string]interface{},depthint,diff*JsonDiff){
blank:=strings.Repeat("",(2*(depth-1)))
longBlank:=strings.Repeat("",(2*(depth)))
diff.Result=diff.Result+"\n"+blank+"{"
forkey,value:=rangejson1{
quotedKey:=fmt.Sprintf("\"%s\"",key)
if_,ok:=json2[key];ok{
switchvalue.(type){
casemap[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+","
}
forkey,value:=rangejson2{
if_,ok:=json1[key];!ok{
diff.HasDiff=true
diff.Result=diff.Result+"\n+"+blank+"\""+key+"\""+":"+marshal(value)+","
}
}
diff.Result=diff.Result+"\n"+blank+"}"
}
funcjsonDiffList(json1,json2[]interface{},depthint,diff*JsonDiff){
blank:=strings.Repeat("",(2*(depth-1)))
longBlank:=strings.Repeat("",(2*(depth)))
diff.Result=diff.Result+"\n"+blank+"["
size:=len(json1)
ifsize>len(json2){
size=len(json2)
}
fori:=0;i<size;i++{
switchjson1[i].(type){
casemap[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+","
}
fori:=size;i<len(json1);i++{
diff.HasDiff=true
diff.Result=diff.Result+"\n-"+blank+marshal(json1[i])
diff.Result=diff.Result+","
}
fori:=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时,将输出区别范围结果向上下各扩展宽度行的结果。
那么,完整代码如下:
packageservice
import(
"encoding/json"
"fmt"
"io/ioutil"
"reflect"
"strings"
)
typeJsonDiffstruct{
HasDiffbool
Resultstring
}
funcJsonCompare(left,rightmap[string]interface{},nint)(string,bool){
diff:=&JsonDiff{HasDiff:false,Result:""}
jsonDiffDict(left,right,1,diff)
ifdiff.HasDiff{
ifn<0{
returndiff.Result,diff.HasDiff
}else{
returnprocessContext(diff.Result,n),diff.HasDiff
}
}
return"",diff.HasDiff
}
funcmarshal(jinterface{})string{
value,_:=json.Marshal(j)
returnstring(value)
}
funcjsonDiffDict(json1,json2map[string]interface{},depthint,diff*JsonDiff){
blank:=strings.Repeat("",(2*(depth-1)))
longBlank:=strings.Repeat("",(2*(depth)))
diff.Result=diff.Result+"\n"+blank+"{"
forkey,value:=rangejson1{
quotedKey:=fmt.Sprintf("\"%s\"",key)
if_,ok:=json2[key];ok{
switchvalue.(type){
casemap[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+","
}
forkey,value:=rangejson2{
if_,ok:=json1[key];!ok{
diff.HasDiff=true
diff.Result=diff.Result+"\n+"+blank+"\""+key+"\""+":"+marshal(value)+","
}
}
diff.Result=diff.Result+"\n"+blank+"}"
}
funcjsonDiffList(json1,json2[]interface{},depthint,diff*JsonDiff){
blank:=strings.Repeat("",(2*(depth-1)))
longBlank:=strings.Repeat("",(2*(depth)))
diff.Result=diff.Result+"\n"+blank+"["
size:=len(json1)
ifsize>len(json2){
size=len(json2)
}
fori:=0;i<size;i++{
switchjson1[i].(type){
casemap[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+","
}
fori:=size;i<len(json1);i++{
diff.HasDiff=true
diff.Result=diff.Result+"\n-"+blank+marshal(json1[i])
diff.Result=diff.Result+","
}
fori:=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+"]"
}
funcprocessContext(diffstring,nint)string{
index1:=strings.Index(diff,"\n-")
index2:=strings.Index(diff,"\n+")
begin:=0
end:=0
ifindex1>=0&&index2>=0{
ifindex1<=index2{
begin=index1
}else{
begin=index2
}
}elseifindex1>=0{
begin=index1
}elseifindex2>=0{
begin=index2
}
index1=strings.LastIndex(diff,"\n-")
index2=strings.LastIndex(diff,"\n+")
ifindex1>=0&&index2>=0{
ifindex1<=index2{
end=index2
}else{
end=index1
}
}elseifindex1>=0{
end=index1
}elseifindex2>=0{
end=index2
}
pre:=diff[0:begin]
post:=diff[end:]
i:=0
l:=begin
fori<n&&l>=0{
i++
l=strings.LastIndex(pre[0:l],"\n")
}
r:=0
j:=0
forj<=n&&r>=0{
j++
t:=strings.Index(post[r:],"\n")
ift>=0{
r=r+t+1
}
}
ifr<0{
r=len(post)
}
returnpre[l+1:]+diff[begin:end]+post[0:r+1]
}
funcLoadJson(pathstring,distinterface{})(errerror){
varcontent[]byte
ifcontent,err=ioutil.ReadFile(path);err==nil{
err=json.Unmarshal(content,dist)
}
returnerr
}
本文内容总结:
原文链接:https://www.cnblogs.com/wangzhao765/p/9662331.html