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

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

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

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

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

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

1
2
3
4
5
6
7
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文件的解析结果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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))
}

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

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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
type JsonDiff struct {
    HasDiff bool
    Result  string
}

func marshal(j interface{}) string {
    value, _ := json.Marshal(j)
    return string(value)
}

//比较json
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 + "{"
    //遍历json1
    for key, value := range json1 {
        quotedKey := fmt.Sprintf("\"%s\"", key)
        //如果json2存在json1相同的key
        if _, ok := json2[key]; ok {
            switch value.(type) {
                //json1的value的类型为json
            case map[string]interface{}:
                //如果json2的value的类型不同
                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 {
                    //如果json2的value的类型相同
                    diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": "
                    //递归比较json
                    jsonDiffDict(value.(map[string]interface{}), json2[key].(map[string]interface{}), depth+1, diff)
                }
                //json1的value的类型为jsonarray
            case []interface{}:
                diff.Result = diff.Result + "\n" + longBlank + quotedKey + ": "
                if _, ok2 := json2[key].([]interface{}); !ok2 {
                    //如果json2的value的类型不同
                    diff.HasDiff = true
                    diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value) + ","
                    diff.Result = diff.Result + "\n+" + blank + quotedKey + ": " + marshal(json2[key])
                } else {
                    //如果json2的value的类型相同
                    jsonDiffList(value.([]interface{}), json2[key].([]interface{}), depth+1, diff)
                }
                //json1的value的类型为简单值
            default:
                //简单值,直接用DeepEqual比较interface{}值
                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 {
            //如果json2不存在json1相同的key
            diff.HasDiff = true
            diff.Result = diff.Result + "\n-" + blank + quotedKey + ": " + marshal(value)
        }
        diff.Result = diff.Result + ","
    }
    //遍历json2,目的是为了找到json2-json1的差集
    for key, value := range json2 {
        //如果json1不存在json2相同的key
        if _, ok := json1[key]; !ok {
            diff.HasDiff = true
            diff.Result = diff.Result + "\n+" + blank + "\"" + key + "\"" + ": " + marshal(value) + ","
        }
    }
    diff.Result = diff.Result + "\n" + blank + "}"
}

//比较jsonList
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时,将输出区别范围结果向上下各扩展宽度行的结果。

那么,完整代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
}

转载:https://www.cnblogs.com/wangzhao765/p/9662331.html