这是之前遇到的一道面试题,后来也确实在工作中实际遇到了。于是记录一下,如何(优雅的)比较两个未知结构的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的更多相关文章

  1. 如何优雅的把后台数据(通常是JSON)轻松渲染到html页面

    如何优雅的把后台数据(通常是JSON)轻松渲染到html页面 在我们做前后端分离的时候,都有遇到过一些看起卡很简答,确无从下手的问题把.比方说后台给了前端一个list集合,集合里面有很多学生,我们现在 ...

  2. python字典转化成json格式。JSONEncoder和JSONDecoder两个类来实现Json字符串和dict类型数据的互相转换

    遇到问题:进行Webservice接口测试时,对接口入参数据进行了处理,变成了dict格式,去进行接口请求报错. 需要转成成json格式,双引号去扩. 如下: 更改代码: # 在Python标准库的j ...

  3. json的两种表示结构(对象和数组).。

    JSON有两种表示结构,对象和数组.对象结构以”{”大括号开始,以”}”大括号结束.中间部分由0或多个以”,”分隔的”key(关键字)/value(值)”对构成,关键字和值之间以”:”分隔,语法结构如 ...

  4. golang结构体json格式化的时间格式

    golang结构体json格式化的时间格式 在我们开发中,经常会解析time.Time 往往前台传过来的时候,是个string 但是我们希望在结构体转成time.Time type Param str ...

  5. Labview一个循环中放两个事件结构会导致前面板锁定的问题

    建议在同一个循环中,只放置一个事件结构.此时,当一个事件发生时,事件结构将对事件进行处理,然后继续循环,事件结构再等待下一个事件发生. 如在同一个循环中放置两个事件结构,只有在两个事件结构都处理了事件 ...

  6. [译]Golang中的优雅重启

    原文 Graceful Restart in Golang 作者 grisha 声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加. ...

  7. 优雅的用两种方式爬网络 txt 文件【雾

    TXT 文件?? (笑 这里爬的是 74xsw (咱好像也不怎么逛的网站)的英雄再临 ... 请注意这并不是教程,只是贴个代码仅供参考而已[雾 这里 用的 getTXT 的方式有两种,一种是每个章节分 ...

  8. golang 开发 Struct 转换成 map 两种方式比较

    原文链接:https://www.jianshu.com/p/81c4304f6d1b 最近做Go开发的时候接触到了一个新的orm第三方框架gorose,在使用的过程中,发现没有类似beego进行直接 ...

  9. golang 1.8 优雅关闭

    // main.go package main import ( "fmt" "log" "net/http" "os" ...

随机推荐

  1. spring: 我是如何解决循环依赖的?

    1.由同事抛的一个问题开始 最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到.平时自认为对spring循环依赖问题还是比较了解的,直到遇到这个和后面的 ...

  2. JavaCV FFmpeg AAC编码

    上次成功通过FFmpeg采集麦克风的PCM数据,这次针对上一次的程序进行了改造,使用AAC编码采集后的数据. (传送门) JavaCV FFmpeg采集麦克风PCM音频数据 采集麦克风数据是一个解码过 ...

  3. git基础-git别名

    Git 并不会在你输入部分命令时自动推断出你想要的命令. 如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名. 这里有一些例子你可以试试: $ ...

  4. Beta冲刺——第五天

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...

  5. Flink SQL 核心概念剖析与编程案例实战

    本次,我们从 0 开始逐步剖析 Flink SQL 的来龙去脉以及核心概念,并附带完整的示例程序,希望对大家有帮助! 本文大纲 一.快速体验 Flink SQL 为了快速搭建环境体验 Flink SQ ...

  6. apply 和 call的用法、区别

    1.JavaScript中函数是对象的方法,如果一个函数不是js对象的方法那一定是全局对象的函数,每个函数的对象都有apply和call方法,即每个对象都有call and apply apply:调 ...

  7. Ubuntu无法ssh远程连接问题 (转)

    [系统]Ubuntu 12.04 server [问题描述]新安装的Ubuntu系统无法直接通过ssh远程连接. [解决办法] 新安装的Ubuntu系统并未安装ssh-server服务,需要自行安装, ...

  8. 杭电OJ1062---Text Reverse(c++)(C++库getline的使用)

    Text Reverse Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Tot ...

  9. Mirai qq机器人 c++版sdk(即用c++写mirai)

    Mirai机器人c++版 前言 类似教程 本文git,gitee地址 c++开发mirai 原理 大概流程 实现 如何使用 注意事项 常见错误 前言 改分支版本以及过时,暂时不再维护 请看最新版kot ...

  10. Linux下安装svn教程

    前言 最近买了新服务器,准备开始弄一些个人的开源项目.有了服务器当然是搞一波svn啦.方便自己的资料上传和下载.于是在此记录搭建svn的方式,方便以后直接使用. 安装 使用yum源进行安装,十分的方便 ...