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 + "\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
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM