Golang中比较两个JSON的最佳方法

Golang中比较两个JSON的最佳方法 在比较两个JSON结构时,我遇到了一些问题。我需要将JSON文件中的内容与服务器中的内容进行比较,以便正确更新服务器的信息。

例如:

如果JSON文件中有以下信息:

{
  "envs": [
    {
      "env": "test",
      "books": [
        { "name": "book1", "read": true },
        { "name": "book2", "read": true },
        { "name": "book3", "read": true }
      ]
    }
  ]
}

并从服务器获取到以下信息:

{
  "name": "Default",
  "envs": [
    "test",
    "stag"
  ],
  "books": [
    {
      "type": "release",
      "name": "book3",
      "envs": [
        {
          "name": "test",
          "enabled": true,
        },
        {
          "name": "stag",
          "enabled": false,
        }
      ]
    },
    {
      "type": "release",
      "name": "book4",
      "envs": [
        {
          "name": "test",
          "enabled": true,
        },
        {
          "name": "stag",
          "enabled": false,
        }
      ]
    }
  ]
}

因此,如果JSON文件中有book1、book2、book3,我需要将其与服务器的响应进行比较,并根据JSON文件(在服务器端)更新信息。

例如,如果JSON文件中有book1、book2、book4,而服务器中有book3、book2。我只需要删除book3、添加book1,最后再添加book4(在服务器端)。

我尝试了一些使用for语句的代码,但难以满足所有要求:

  1. 当服务器中的书籍数量为空时。
  2. 当服务器中的书籍数量多于JSON文件中的书籍时。
  3. 当JSON文件中的书籍数量多于服务器响应中的书籍时。

我发现使用嵌套的for循环并不容易实现。有没有办法实现我想要的功能,或者有没有清晰的示例?

谢谢。


更多关于Golang中比较两个JSON的最佳方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

这正是我一直在寻找的。谢谢。

更多关于Golang中比较两个JSON的最佳方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你能展示一下你目前的代码吗?并且能详细说明你提到的三个问题吗?另外,env 有什么特殊含义吗(例如,你是仅通过名称来匹配服务器中的书籍和 JSON 中的书籍,还是需要名称和 env 都相同)?

如果我理解正确的话,你并不希望环境部分重复;你只是希望将功能追加到单个环境中。如果这是你想要的,那么你可以不创建 []Environments 切片,而是创建一个单一的环境,并将功能追加到其中:Go Playground - The Go Programming Language

你的代码似乎混合了JSON语法和Go语法。如果你将这段代码:

test = append(test, File{environments:{[{environment:i.name, features:[{name: i.name, enabled: i.enabled}]}]}})

改为这样:

			test = append(test, File{
				Environments: []Environment{
					{Environment: i.Name, Features: []Features{
						{Name: i.Name, Enabled: i.Enabled},
					}},
				},
			})

这将帮助你解决第一个错误。然后,我必须将你未声明的 test 变量替换为 variable 变量:

			variable = append(variable, File{
				Environments: []Environment{
					{Environment: i.Name, Features: []Features{
						{Name: i.Name, Enabled: i.Enabled},
					}},
				},
			})

Go Playground

Go Playground - The Go Programming Language

@skillian 现在好多了,谢谢。

不过,我在想是否能轻松地避免每次都重复“environments”这个词……

基本上,我只想得到类似这样的输出:

{
  "environments": [
    {
      "evironment": "dev",
      "features": [
        { "name": "XXXA", "enabled": true },
        { "name": "XXXB", "enabled": true },
        { "name": "XXXD", "enabled": true }
      ]
    }
  ]
}

我对 append 做了一些修改,但无法避免重复 environments 这个词。这是否是我可以在切片中做到的事情?

我不太确定是否可以在 Environments 结构体切片 var variable []Environments 中,只追加“environments”这个词之后的数据。

你认为我需要使用切片之外的其他东西吗?

var variable []Environments
	for _, e := range x.Features {
		for _, i := range e.Environments {
			variable = append(variable, Environments{
				Environments: []Environment{
					{Environment: i.Name, Features: []Features{
						{Name: e.Name, Enabled: i.Enabled},
					}},
				},
			})
		}
	}

这是输出:

[{"environments":[{"environment":"dev","features":[{"name":"Atest","enabled":true}]}]},{"environments":[{"environment":"dev","features":[{"name":"Btest","enabled":true}]}]}]

Go Playground - The Go Programming Language

我将重述这个问题。这是我关于Go语言的初期问题之一,当时我没有注意到一些细节。

  1. 我需要将变量 json_response 中的JSON数据转换为与变量 json_file 相同的结构。json_response 中的某些字段必须被忽略,只选择和 json_file 相同的字段。
  2. 当两个“变量”包含相同的结构后,我必须使用某种自定义函数来比较它们。
compare(json_file, json_response) -> 服务器上的旧功能(将在服务器上删除)
compare(json_response, json_file) -> 文件中的新功能(将在服务器上添加)

以下是我获取到的JSON变量:

//变量 json 数据
json_file := `{
  "environments": [
    {
      "evironment": "dev",
      "features": [
        { "name": "featA", "enabled": true },
        { "name": "featB", "enabled": true },
        { "name": "featD", "enabled": true }
      ]
    }
  ]
}`

// 服务器的响应
json_response := `{
		"environments": ["development", "production"],
		"features": [{
			"type": "release",
			"name": "featA",
			"stale": false,
			"environments": [{
				"name": "dev",
				"enabled": true,
				"type": "dev",
				"sortOrder": 100
			}, {
				"name": "stag",
				"enabled": false,
				"type": "stag",
				"sortOrder": 200
			}]
		}, {
			"type": "release",
			"name": "featC",
			"stale": false,
			"environments": [{
				"name": "dev",
				"enabled": true,
				"type": "dev",
				"sortOrder": 100
			}, {
				"name": "stag",
				"enabled": false,
				"type": "stag",
				"sortOrder": 200
			}]
		}],
		"members": 1,
		"version": 1
	}`

两个变量可能包含不同数量的功能(大小不同),这就是问题所在,尽管可以使用像 DeepEqual 这样的内置映射函数来比较,但直接比较它们并不容易。

到目前为止我尝试了以下方法:

  1. 我为两个变量定义了结构体,以便可以使用结构体工作。
  2. 将变量中的数据解组到结构体中。
  3. 我创建了一个结构体切片,以便可以在那里追加信息。
  4. 使用 for 循环遍历结构体,从响应中获取信息并将其复制到切片中。
// 为两个变量定义结构

// json_file 的 JSON 结构
type Features struct {
	Name    string `json:"name"`
	Enabled bool   `json:"enabled"`
}

type Environment struct {
	Environment string     `json:"environment"`
	Features    []Features `json:"features"`
}

type File struct {
	File []Environment `json:"environments"`
}

// json_response 的 JSON 结构
type Response struct {
	Name         string   `json:"name"`
	Environments []string `json:"environments"`
	Features     []struct {
		Type         string      `json:"type"`
		Name         string      `json:"name"`
		Stale        bool        `json:"stale"`
		Environments []struct {
			Name      string `json:"name"`
			Enabled   bool   `json:"enabled"`
			Type      string `json:"type"`
			SortOrder int    `json:"sortOrder"`
		} `json:"environments"`
	} `json:"features"`
	Members int `json:"members"`
	Version int `json:"version"`
}

func main() {
       
       // 结构体类型的变量
	var json_file_struct File
        var json_response_struct Response

      // 解组 JSON 数据以存储到结构体类型的变量中
	err := json.Unmarshal([]byte(json_file), &json_file_struct)
	if err != nil {
		fmt.Println(err)
	}

       err := json.Unmarshal([]byte(json_response), &json_response_struct)
	if err != nil {
		fmt.Println(err)
	}

       // 遍历结构体变量 json_response_struct 以获取与 json_file_struct 相同的字段信息,以便稍后进行比较。
      // 为此,我创建了一个基于 Fields 结构体的切片,以便可以在那里追加信息。
	var variable []File
	for _, e := range json_response_struct.features {
		for _, i := range e.environments {
		    test = append(test, File{environments:{[{environment:i.name, features:[{name: i.name, enabled: i.enabled}]}]}})
		}

	}

     // 其余代码用于创建两个结构体之间的比较,以便我可以在服务器上创建/删除这些功能
}

我试图避免在 for 循环 中使用 reflect,因为我认为在这种情况下没有必要。

前面的循环无法工作。在 test=append(...) 处,它给出了一个 syntax expected expression 错误。如果你能建议一个更好的方法来实现我试图做的事情,我将不胜感激。

在Go中比较两个JSON结构的最佳方法是使用reflect.DeepEqual配合JSON反序列化,或者使用专门的JSON比较库如github.com/google/go-cmp/cmp。对于你的具体需求,需要比较并同步两个不同结构的JSON数据,这里提供两种解决方案:

方案1:使用反射和自定义比较逻辑

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type FileBook struct {
    Name string `json:"name"`
    Read bool   `json:"read"`
}

type FileEnv struct {
    Env   string      `json:"env"`
    Books []FileBook  `json:"books"`
}

type FileData struct {
    Envs []FileEnv `json:"envs"`
}

type ServerEnv struct {
    Name    string `json:"name"`
    Enabled bool   `json:"enabled"`
}

type ServerBook struct {
    Type  string      `json:"type"`
    Name  string      `json:"name"`
    Envs  []ServerEnv `json:"envs"`
}

type ServerData struct {
    Name  string        `json:"name"`
    Envs  []string      `json:"envs"`
    Books []ServerBook  `json:"books"`
}

func compareAndUpdate(fileJSON, serverJSON []byte) ([]string, []string, []string) {
    var fileData FileData
    var serverData ServerData
    
    json.Unmarshal(fileJSON, &fileData)
    json.Unmarshal(serverJSON, &serverData)
    
    fileBooks := make(map[string]bool)
    serverBooks := make(map[string]bool)
    
    for _, env := range fileData.Envs {
        for _, book := range env.Books {
            fileBooks[book.Name] = true
        }
    }
    
    for _, book := range serverData.Books {
        serverBooks[book.Name] = true
    }
    
    var toAdd []string
    var toRemove []string
    var toKeep []string
    
    for bookName := range fileBooks {
        if !serverBooks[bookName] {
            toAdd = append(toAdd, bookName)
        } else {
            toKeep = append(toKeep, bookName)
        }
    }
    
    for bookName := range serverBooks {
        if !fileBooks[bookName] {
            toRemove = append(toRemove, bookName)
        }
    }
    
    return toAdd, toRemove, toKeep
}

func main() {
    fileJSON := []byte(`{
        "envs": [{
            "env": "test",
            "books": [
                { "name": "book1", "read": true },
                { "name": "book2", "read": true },
                { "name": "book4", "read": true }
            ]
        }]
    }`)
    
    serverJSON := []byte(`{
        "name": "Default",
        "envs": ["test", "stag"],
        "books": [
            {
                "type": "release",
                "name": "book3",
                "envs": [
                    { "name": "test", "enabled": true },
                    { "name": "stag", "enabled": false }
                ]
            },
            {
                "type": "release",
                "name": "book2",
                "envs": [
                    { "name": "test", "enabled": true },
                    { "name": "stag", "enabled": false }
                ]
            }
        ]
    }`)
    
    toAdd, toRemove, toKeep := compareAndUpdate(fileJSON, serverJSON)
    
    fmt.Printf("需要添加的书籍: %v\n", toAdd)
    fmt.Printf("需要删除的书籍: %v\n", toRemove)
    fmt.Printf("需要保留的书籍: %v\n", toKeep)
}

方案2:使用go-cmp进行深度比较

package main

import (
    "encoding/json"
    "fmt"
    
    "github.com/google/go-cmp/cmp"
)

func compareJSONStructures(fileJSON, serverJSON []byte) (bool, string) {
    var fileData interface{}
    var serverData interface{}
    
    json.Unmarshal(fileJSON, &fileData)
    json.Unmarshal(serverJSON, &serverData)
    
    diff := cmp.Diff(fileData, serverData)
    if diff != "" {
        return false, diff
    }
    return true, ""
}

func extractBookNames(data interface{}) map[string]bool {
    books := make(map[string]bool)
    
    switch v := data.(type) {
    case map[string]interface{}:
        if envs, ok := v["envs"].([]interface{}); ok {
            for _, env := range envs {
                if envMap, ok := env.(map[string]interface{}); ok {
                    if bookList, ok := envMap["books"].([]interface{}); ok {
                        for _, book := range bookList {
                            if bookMap, ok := book.(map[string]interface{}); ok {
                                if name, ok := bookMap["name"].(string); ok {
                                    books[name] = true
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    return books
}

func main() {
    fileJSON := []byte(`{
        "envs": [{
            "env": "test",
            "books": [
                { "name": "book1", "read": true },
                { "name": "book2", "read": true },
                { "name": "book3", "read": true }
            ]
        }]
    }`)
    
    serverJSON := []byte(`{
        "name": "Default",
        "envs": ["test", "stag"],
        "books": [
            {
                "type": "release",
                "name": "book3",
                "envs": [
                    { "name": "test", "enabled": true },
                    { "name": "stag", "enabled": false }
                ]
            }
        ]
    }`)
    
    // 比较整体结构
    equal, diff := compareJSONStructures(fileJSON, serverJSON)
    fmt.Printf("结构是否相等: %v\n", equal)
    if !equal {
        fmt.Printf("差异: %s\n", diff)
    }
    
    // 提取并比较书籍名称
    var fileData, serverData interface{}
    json.Unmarshal(fileJSON, &fileData)
    json.Unmarshal(serverJSON, &serverData)
    
    fileBooks := extractBookNames(fileData)
    
    var serverBooks = make(map[string]bool)
    if serverMap, ok := serverData.(map[string]interface{}); ok {
        if books, ok := serverMap["books"].([]interface{}); ok {
            for _, book := range books {
                if bookMap, ok := book.(map[string]interface{}); ok {
                    if name, ok := bookMap["name"].(string); ok {
                        serverBooks[name] = true
                    }
                }
            }
        }
    }
    
    fmt.Printf("\n文件中的书籍: %v\n", fileBooks)
    fmt.Printf("服务器中的书籍: %v\n", serverBooks)
}

方案3:通用JSON比较函数

package main

import (
    "encoding/json"
    "fmt"
)

func compareJSON(a, b []byte) (bool, error) {
    var j1, j2 interface{}
    
    if err := json.Unmarshal(a, &j1); err != nil {
        return false, err
    }
    if err := json.Unmarshal(b, &j2); err != nil {
        return false, err
    }
    
    return deepEqual(j1, j2), nil
}

func deepEqual(a, b interface{}) bool {
    switch aVal := a.(type) {
    case map[string]interface{}:
        bVal, ok := b.(map[string]interface{})
        if !ok {
            return false
        }
        if len(aVal) != len(bVal) {
            return false
        }
        for key, aItem := range aVal {
            bItem, exists := bVal[key]
            if !exists || !deepEqual(aItem, bItem) {
                return false
            }
        }
        return true
        
    case []interface{}:
        bVal, ok := b.([]interface{})
        if !ok {
            return false
        }
        if len(aVal) != len(bVal) {
            return false
        }
        for i := range aVal {
            if !deepEqual(aVal[i], bVal[i]) {
                return false
            }
        }
        return true
        
    default:
        return a == b
    }
}

func getUpdateOperations(fileJSON, serverJSON []byte) (add, remove []string) {
    var fileData map[string]interface{}
    var serverData map[string]interface{}
    
    json.Unmarshal(fileJSON, &fileData)
    json.Unmarshal(serverJSON, &serverData)
    
    fileBooks := extractBooks(fileData)
    serverBooks := extractServerBooks(serverData)
    
    for book := range fileBooks {
        if !serverBooks[book] {
            add = append(add, book)
        }
    }
    
    for book := range serverBooks {
        if !fileBooks[book] {
            remove = append(remove, book)
        }
    }
    
    return add, remove
}

func extractBooks(data map[string]interface{}) map[string]bool {
    books := make(map[string]bool)
    
    if envs, ok := data["envs"].([]interface{}); ok {
        for _, env := range envs {
            if envMap, ok := env.(map[string]interface{}); ok {
                if bookList, ok := envMap["books"].([]interface{}); ok {
                    for _, book := range bookList {
                        if bookMap, ok := book.(map[string]interface{}); ok {
                            if name, ok := bookMap["name"].(string); ok {
                                books[name] = true
                            }
                        }
                    }
                }
            }
        }
    }
    
    return books
}

func extractServerBooks(data map[string]interface{}) map[string]bool {
    books := make(map[string]bool)
    
    if bookList, ok := data["books"].([]interface{}); ok {
        for _, book := range bookList {
            if bookMap, ok := book.(map[string]interface{}); ok {
                if name, ok := bookMap["name"].(string); ok {
                    books[name] = true
                }
            }
        }
    }
    
    return books
}

func main() {
    fileJSON := []byte(`{
        "envs": [{
            "env": "test",
            "books": [
                { "name": "book1", "read": true },
                { "name": "book2", "read": true },
                { "name": "book4", "read": true }
            ]
        }]
    }`)
    
    serverJSON := []byte(`{
        "name": "Default",
        "envs": ["test", "stag"],
        "books": [
            { "type": "release", "name": "book3" },
            { "type": "release", "name": "book2" }
        ]
    }`)
    
    add, remove := getUpdateOperations(fileJSON, serverJSON)
    
    fmt.Printf("需要添加: %v\n", add)
    fmt.Printf("需要删除: %v\n", remove)
    
    // 处理边界情况
    emptyServerJSON := []byte(`{"books": []}`)
    add2, remove2 := getUpdateOperations(fileJSON, emptyServerJSON)
    fmt.Printf("\n服务器为空时需要添加: %v\n", add2)
    fmt.Printf("服务器为空时需要删除: %v\n", remove2)
}

这些方案可以处理你提到的所有边界情况:

  1. 服务器中的书籍数量为空时
  2. 服务器中的书籍数量多于JSON文件时
  3. JSON文件中的书籍数量多于服务器时

第一种方案使用结构体映射,适合结构固定的JSON;第二种方案使用go-cmp库,适合需要详细差异信息的场景;第三种方案使用通用接口,适合动态JSON结构。

回到顶部