zoukankan      html  css  js  c++  java
  • 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 + "
    " + 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 + "
    -" + blank + quotedKey + ": " + marshal(value) + ","
                        diff.Result = diff.Result + "
    +" + blank + quotedKey + ": " + marshal(json2[key])
                    } else {
                        diff.Result = diff.Result + "
    " + longBlank + quotedKey + ": "
                        jsonDiffDict(value.(map[string]interface{}), json2[key].(map[string]interface{}), depth+1, diff)
                    }
                case []interface{}:
                    diff.Result = diff.Result + "
    " + longBlank + quotedKey + ": "
                    if _, ok2 := json2[key].([]interface{}); !ok2 {
                        diff.HasDiff = true
                        diff.Result = diff.Result + "
    -" + blank + quotedKey + ": " + marshal(value) + ","
                        diff.Result = diff.Result + "
    +" + 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 + "
    -" + blank + quotedKey + ": " + marshal(value) + ","
                        diff.Result = diff.Result + "
    +" + blank + quotedKey + ": " + marshal(json2[key])
                    } else {
                        diff.Result = diff.Result + "
    " + longBlank + quotedKey + ": " + marshal(value)
                    }
                }
            } else {
                diff.HasDiff = true
                diff.Result = diff.Result + "
    -" + 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 + "
    +" + blank + """ + key + """ + ": " + marshal(value) + ","
            }
        }
        diff.Result = diff.Result + "
    " + 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 + "
    " + 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 + "
    -" + blank + marshal(json1[i]) + ","
                    diff.Result = diff.Result + "
    +" + blank + marshal(json2[i])
                }
            case []interface{}:
                if _, ok2 := json2[i].([]interface{}); !ok2 {
                    diff.HasDiff = true
                    diff.Result = diff.Result + "
    -" + blank + marshal(json1[i]) + ","
                    diff.Result = diff.Result + "
    +" + 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 + "
    -" + blank + marshal(json1[i]) + ","
                    diff.Result = diff.Result + "
    +" + blank + marshal(json2[i])
                } else {
                    diff.Result = diff.Result + "
    " + longBlank + marshal(json1[i])
                }
            }
            diff.Result = diff.Result + ","
        }
        for i := size; i < len(json1); i++ {
            diff.HasDiff = true
            diff.Result = diff.Result + "
    -" + blank + marshal(json1[i])
            diff.Result = diff.Result + ","
        }
        for i := size; i < len(json2); i++ {
            diff.HasDiff = true
            diff.Result = diff.Result + "
    +" + blank + marshal(json2[i])
            diff.Result = diff.Result + ","
        }
        diff.Result = diff.Result + "
    " + 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 + "
    " + 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 + "
    -" + blank + quotedKey + ": " + marshal(value) + ","
                        diff.Result = diff.Result + "
    +" + blank + quotedKey + ": " + marshal(json2[key])
                    } else {
                        diff.Result = diff.Result + "
    " + longBlank + quotedKey + ": "
                        jsonDiffDict(value.(map[string]interface{}), json2[key].(map[string]interface{}), depth+1, diff)
                    }
                case []interface{}:
                    diff.Result = diff.Result + "
    " + longBlank + quotedKey + ": "
                    if _, ok2 := json2[key].([]interface{}); !ok2 {
                        diff.HasDiff = true
                        diff.Result = diff.Result + "
    -" + blank + quotedKey + ": " + marshal(value) + ","
                        diff.Result = diff.Result + "
    +" + 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 + "
    -" + blank + quotedKey + ": " + marshal(value) + ","
                        diff.Result = diff.Result + "
    +" + blank + quotedKey + ": " + marshal(json2[key])
                    } else {
                        diff.Result = diff.Result + "
    " + longBlank + quotedKey + ": " + marshal(value)
                    }
                }
            } else {
                diff.HasDiff = true
                diff.Result = diff.Result + "
    -" + 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 + "
    +" + blank + """ + key + """ + ": " + marshal(value) + ","
            }
        }
        diff.Result = diff.Result + "
    " + 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 + "
    " + 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 + "
    -" + blank + marshal(json1[i]) + ","
                    diff.Result = diff.Result + "
    +" + blank + marshal(json2[i])
                }
            case []interface{}:
                if _, ok2 := json2[i].([]interface{}); !ok2 {
                    diff.HasDiff = true
                    diff.Result = diff.Result + "
    -" + blank + marshal(json1[i]) + ","
                    diff.Result = diff.Result + "
    +" + 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 + "
    -" + blank + marshal(json1[i]) + ","
                    diff.Result = diff.Result + "
    +" + blank + marshal(json2[i])
                } else {
                    diff.Result = diff.Result + "
    " + longBlank + marshal(json1[i])
                }
            }
            diff.Result = diff.Result + ","
        }
        for i := size; i < len(json1); i++ {
            diff.HasDiff = true
            diff.Result = diff.Result + "
    -" + blank + marshal(json1[i])
            diff.Result = diff.Result + ","
        }
        for i := size; i < len(json2); i++ {
            diff.HasDiff = true
            diff.Result = diff.Result + "
    +" + blank + marshal(json2[i])
            diff.Result = diff.Result + ","
        }
        diff.Result = diff.Result + "
    " + blank + "]"
    }
    
    func processContext(diff string, n int) string {
        index1 := strings.Index(diff, "
    -")
        index2 := strings.Index(diff, "
    +")
        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, "
    -")
        index2 = strings.LastIndex(diff, "
    +")
        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], "
    ")
        }
        r := 0
        j := 0
        for j <= n && r >= 0 {
            j++
            t := strings.Index(post[r:], "
    ")
            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
    }
  • 相关阅读:
    C语言中const关键字的用法
    LDO和DC-DC的概念,区别及优缺点
    Ubuntu下几个命令行方式使用的图片浏览工具
    I2C和I2S的区别和使用方法
    scikit-image 图像处理库介绍
    USB协议介绍
    Ubuntu 16.04 python和OpenCV安装
    一种基于python的人脸识别开源系统
    numpy 介绍
    python enumerate用法总结
  • 原文地址:https://www.cnblogs.com/wangzhao765/p/9662331.html
Copyright © 2011-2022 走看看