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语句的代码,但难以满足所有要求:
- 当服务器中的书籍数量为空时。
- 当服务器中的书籍数量多于JSON文件中的书籍时。
- 当JSON文件中的书籍数量多于服务器响应中的书籍时。
我发现使用嵌套的for循环并不容易实现。有没有办法实现我想要的功能,或者有没有清晰的示例?
谢谢。
更多关于Golang中比较两个JSON的最佳方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你能展示一下你目前的代码吗?并且能详细说明你提到的三个问题吗?另外,env 有什么特殊含义吗(例如,你是仅通过名称来匹配服务器中的书籍和 JSON 中的书籍,还是需要名称和 env 都相同)?
如果我理解正确的话,你并不希望环境部分重复;你只是希望将功能追加到单个环境中。如果这是你想要的,那么你可以不创建 []Environments 切片,而是创建一个单一的环境,并将功能追加到其中: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语言的初期问题之一,当时我没有注意到一些细节。
- 我需要将变量
json_response中的JSON数据转换为与变量json_file相同的结构。json_response中的某些字段必须被忽略,只选择和json_file相同的字段。 - 当两个“变量”包含相同的结构后,我必须使用某种自定义函数来比较它们。
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 这样的内置映射函数来比较,但直接比较它们并不容易。
到目前为止我尝试了以下方法:
- 我为两个变量定义了结构体,以便可以使用结构体工作。
- 将变量中的数据解组到结构体中。
- 我创建了一个结构体切片,以便可以在那里追加信息。
- 使用 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)
}
这些方案可以处理你提到的所有边界情况:
- 服务器中的书籍数量为空时
- 服务器中的书籍数量多于JSON文件时
- JSON文件中的书籍数量多于服务器时
第一种方案使用结构体映射,适合结构固定的JSON;第二种方案使用go-cmp库,适合需要详细差异信息的场景;第三种方案使用通用接口,适合动态JSON结构。



