Golang之如何(优雅的)比较两个未知结构的json

这是之前遇到的一道面试题,后来也确实在工作中实际遇到了。于是记录一下,如何(优雅的)比较两个未知结构的json。

假设,现在有两个简单的json文件。

1{ 2 "id":1, 3 "name":"testjson01", 4 "isadmin":true 5} 6 7{ 8 "isadmin":true, 9 "name":"testjson01", 10 "id":1 11}

那么,如何比较这两个json的内容是否相同呢?

首先,最基本的方法就是利用golang的反射提供的DeepEqual()

假设我们有一个读取json文件的函数如下:

1func LoadJson(path string, dist interface{}) (err error) { 2 var content []byte 3 if content, err = ioutil.ReadFile(path); err == nil { 4 err = json.Unmarshal(content, dist) 5 } 6 return err 7}

那么,我们可以调用该函数来读取json文件。由于json的结构是未知的,所以我们需要声明一个map[string]interface{}来存放对json文件的解析结果。

1func main() { 2 var ( 3 json1 map[string]interface{} 4 json2 map[string]interface{} 5 ) 6 if err := service.LoadJson("./etc/json/json01.json", &json1); err != nil { 7 fmt.Println(err) 8 } 9 if err := service.LoadJson("./etc/json/json02.json", &json2); err != nil { 10 fmt.Println(err) 11 } 12 fmt.Println(reflect.DeepEqual(json1, json2)) 13}

这会在终端中输出一个比较的结果:

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的内容;对于不同的部分,我们分别记录下两者的区别。

1type JsonDiff struct { 2 HasDiff bool 3 Result string 4} 5 6func marshal(j interface{}) string { 7 value, _ := json.Marshal(j) 8 return string(value) 9} 10 11func jsonDiffDict(json1, json2 map[string]interface{}, depth int, diff *JsonDiff) { 12 blank := strings.Repeat(" ", (2 * (depth - 1))) 13 longBlank := strings.Repeat(" ", (2 * (depth))) 14 diff.Result = diff.Result + "\n" + blank + "{" 15 for key, value := range json1 { 16 quotedKey := fmt.Sprintf("\"%s\"", key) 17 if _, ok := json2[key]; ok { 18 switch value.(type) { 19 case map[string]interface{}: 20 if _, ok2 := json2[key].(map[string]interface{}); !ok2 { 21 diff.HasDiff = true 22 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + "," 23 diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key]) 24 } else { 25 diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " 26 jsonDiffDict(value.(map[string]interface{}), json2[key].(map[string]interface{}), depth+1, diff) 27 } 28 case []interface{}: 29 diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " 30 if _, ok2 := json2[key].([]interface{}); !ok2 { 31 diff.HasDiff = true 32 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + "," 33 diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key]) 34 } else { 35 jsonDiffList(value.([]interface{}), json2[key].([]interface{}), depth+1, diff) 36 } 37 default: 38 if !reflect.DeepEqual(value, json2[key]) { 39 diff.HasDiff = true 40 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + "," 41 diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key]) 42 } else { 43 diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " + marshal(value) 44 } 45 } 46 } else { 47 diff.HasDiff = true 48 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) 49 } 50 diff.Result = diff.Result + "," 51 } 52 for key, value := range json2 { 53 if _, ok := json1[key]; !ok { 54 diff.HasDiff = true 55 diff.Result = diff.Result + "\n+" + blank + "\"" + key + "\"" + ": " + marshal(value) + "," 56 } 57 } 58 diff.Result = diff.Result + "\n" + blank + "}" 59} 60 61func jsonDiffList(json1, json2 []interface{}, depth int, diff *JsonDiff) { 62 blank := strings.Repeat(" ", (2 * (depth - 1))) 63 longBlank := strings.Repeat(" ", (2 * (depth))) 64 diff.Result = diff.Result + "\n" + blank + "[" 65 size := len(json1) 66 if size > len(json2) { 67 size = len(json2) 68 } 69 for i := 0; i < size; i++ { 70 switch json1[i].(type) { 71 case map[string]interface{}: 72 if _, ok := json2[i].(map[string]interface{}); ok { 73 jsonDiffDict(json1[i].(map[string]interface{}), json2[i].(map[string]interface{}), depth+1, diff) 74 } else { 75 diff.HasDiff = true 76 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + "," 77 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 78 } 79 case []interface{}: 80 if _, ok2 := json2[i].([]interface{}); !ok2 { 81 diff.HasDiff = true 82 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + "," 83 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 84 } else { 85 jsonDiffList(json1[i].([]interface{}), json2[i].([]interface{}), depth+1, diff) 86 } 87 default: 88 if !reflect.DeepEqual(json1[i], json2[i]) { 89 diff.HasDiff = true 90 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + "," 91 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 92 } else { 93 diff.Result = diff.Result + "\n" + longBlank + marshal(json1[i]) 94 } 95 } 96 diff.Result = diff.Result + "," 97 } 98 for i := size; i < len(json1); i++ { 99 diff.HasDiff = true 100 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) 101 diff.Result = diff.Result + "," 102 } 103 for i := size; i < len(json2); i++ { 104 diff.HasDiff = true 105 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 106 diff.Result = diff.Result + "," 107 } 108 diff.Result = diff.Result + "\n" + blank + "]" 109}

因为可能会出现,json很长,但是区别只有一两行这种情况,所以我们还需要设定一个输出范围宽度的设定。

当宽度<0时,输出完整的json比较结果。当宽度>=0时,将输出区别范围结果向上下各扩展宽度行的结果。

那么,完整代码如下:

1package service 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "reflect" 8 "strings" 9) 10 11type JsonDiff struct { 12 HasDiff bool 13 Result string 14} 15 16func JsonCompare(left, right map[string]interface{}, n int) (string, bool) { 17 diff := &JsonDiff{HasDiff: false, Result: ""} 18 jsonDiffDict(left, right, 1, diff) 19 if diff.HasDiff { 20 if n < 0 { 21 return diff.Result, diff.HasDiff 22 } else { 23 return processContext(diff.Result, n), diff.HasDiff 24 } 25 } 26 return "", diff.HasDiff 27} 28 29func marshal(j interface{}) string { 30 value, _ := json.Marshal(j) 31 return string(value) 32} 33 34func jsonDiffDict(json1, json2 map[string]interface{}, depth int, diff *JsonDiff) { 35 blank := strings.Repeat(" ", (2 * (depth - 1))) 36 longBlank := strings.Repeat(" ", (2 * (depth))) 37 diff.Result = diff.Result + "\n" + blank + "{" 38 for key, value := range json1 { 39 quotedKey := fmt.Sprintf("\"%s\"", key) 40 if _, ok := json2[key]; ok { 41 switch value.(type) { 42 case map[string]interface{}: 43 if _, ok2 := json2[key].(map[string]interface{}); !ok2 { 44 diff.HasDiff = true 45 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + "," 46 diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key]) 47 } else { 48 diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " 49 jsonDiffDict(value.(map[string]interface{}), json2[key].(map[string]interface{}), depth+1, diff) 50 } 51 case []interface{}: 52 diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " 53 if _, ok2 := json2[key].([]interface{}); !ok2 { 54 diff.HasDiff = true 55 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + "," 56 diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key]) 57 } else { 58 jsonDiffList(value.([]interface{}), json2[key].([]interface{}), depth+1, diff) 59 } 60 default: 61 if !reflect.DeepEqual(value, json2[key]) { 62 diff.HasDiff = true 63 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + "," 64 diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key]) 65 } else { 66 diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": " + marshal(value) 67 } 68 } 69 } else { 70 diff.HasDiff = true 71 diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) 72 } 73 diff.Result = diff.Result + "," 74 } 75 for key, value := range json2 { 76 if _, ok := json1[key]; !ok { 77 diff.HasDiff = true 78 diff.Result = diff.Result + "\n+" + blank + "\"" + key + "\"" + ": " + marshal(value) + "," 79 } 80 } 81 diff.Result = diff.Result + "\n" + blank + "}" 82} 83 84func jsonDiffList(json1, json2 []interface{}, depth int, diff *JsonDiff) { 85 blank := strings.Repeat(" ", (2 * (depth - 1))) 86 longBlank := strings.Repeat(" ", (2 * (depth))) 87 diff.Result = diff.Result + "\n" + blank + "[" 88 size := len(json1) 89 if size > len(json2) { 90 size = len(json2) 91 } 92 for i := 0; i < size; i++ { 93 switch json1[i].(type) { 94 case map[string]interface{}: 95 if _, ok := json2[i].(map[string]interface{}); ok { 96 jsonDiffDict(json1[i].(map[string]interface{}), json2[i].(map[string]interface{}), depth+1, diff) 97 } else { 98 diff.HasDiff = true 99 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + "," 100 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 101 } 102 case []interface{}: 103 if _, ok2 := json2[i].([]interface{}); !ok2 { 104 diff.HasDiff = true 105 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + "," 106 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 107 } else { 108 jsonDiffList(json1[i].([]interface{}), json2[i].([]interface{}), depth+1, diff) 109 } 110 default: 111 if !reflect.DeepEqual(json1[i], json2[i]) { 112 diff.HasDiff = true 113 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) + "," 114 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 115 } else { 116 diff.Result = diff.Result + "\n" + longBlank + marshal(json1[i]) 117 } 118 } 119 diff.Result = diff.Result + "," 120 } 121 for i := size; i < len(json1); i++ { 122 diff.HasDiff = true 123 diff.Result = diff.Result + "\n-" + blank + marshal(json1[i]) 124 diff.Result = diff.Result + "," 125 } 126 for i := size; i < len(json2); i++ { 127 diff.HasDiff = true 128 diff.Result = diff.Result + "\n+" + blank + marshal(json2[i]) 129 diff.Result = diff.Result + "," 130 } 131 diff.Result = diff.Result + "\n" + blank + "]" 132} 133 134func processContext(diff string, n int) string { 135 index1 := strings.Index(diff, "\n-") 136 index2 := strings.Index(diff, "\n+") 137 begin := 0 138 end := 0 139 if index1 >= 0 && index2 >= 0 { 140 if index1 <= index2 { 141 begin = index1 142 } else { 143 begin = index2 144 } 145 } else if index1 >= 0 { 146 begin = index1 147 } else if index2 >= 0 { 148 begin = index2 149 } 150 index1 = strings.LastIndex(diff, "\n-") 151 index2 = strings.LastIndex(diff, "\n+") 152 if index1 >= 0 && index2 >= 0 { 153 if index1 <= index2 { 154 end = index2 155 } else { 156 end = index1 157 } 158 } else if index1 >= 0 { 159 end = index1 160 } else if index2 >= 0 { 161 end = index2 162 } 163 pre := diff[0:begin] 164 post := diff[end:] 165 i := 0 166 l := begin 167 for i < n && l >= 0 { 168 i++ 169 l = strings.LastIndex(pre[0:l], "\n") 170 } 171 r := 0 172 j := 0 173 for j <= n && r >= 0 { 174 j++ 175 t := strings.Index(post[r:], "\n") 176 if t >= 0 { 177 r = r + t + 1 178 } 179 } 180 if r < 0 { 181 r = len(post) 182 } 183 return pre[l+1:] + diff[begin:end] + post[0:r+1] 184} 185 186func LoadJson(path string, dist interface{}) (err error) { 187 var content []byte 188 if content, err = ioutil.ReadFile(path); err == nil { 189 err = json.Unmarshal(content, dist) 190 } 191 return err 192}
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang

Golang之如何(优雅的)比较两个未知结构的json - HelloWorld