Golang中动态JSON处理的求助指南

Golang中动态JSON处理的求助指南 我正在尝试解析来自另一个应用程序的一些JSON数据,但遇到了困难。由于传入的JSON格式未知,我无法使用静态结构体。我在网上找到了一些部分有效的代码,但它似乎无法处理最高层级是数组的情况。我不确定如何修复它。以下是代码:

package main

import (
	"fmt"
	"encoding/json"
)

func get(m interface{}, path ...interface{}) interface{} {
	for _, p := range path {
		switch idx := p.(type) {
		case string:
			m = m.(map[string]interface{})[idx]
		case int:
			m = m.([]interface{})[idx]
		}
	}
	return m
}

func main() {

	input :=	`
[{
	"_id": "ansible_facts_localhost",
	"data": {
		"ansible_iscsi_iqn": "",
		"vcenter_centos": {
			"msg": "All items completed",
			"changed": false,
			"results": [
				{
				"ansible_loop_var": "item",
				"instance": {
					"hw_name": "mytst01",
					"hw_power_status": "poweredOn",
					"snapshots": [],
					"guest_question": null,
					"hw_interfaces": [
						"eth0",
						"eth1"
					],
					"hw_guest_id": null,
					"current_snapshot": null
				}
				}
			]
		}
	}
},{
	"_id": "ansible_facts_test02",
	"data": {
		"ansible_processor_count": 2,
		"gather_subset": [
			"all"
		],
		"ansible_product_version": "None",
		"ansible_service_mgr": "systemd",
		"ansible_fibre_channel_wwn": [],
		"module_setup": true,
		"ansible_memtotal_mb": 7821,
		"ansible_hostnqn": "",
		"ansible_distribution_version": "7.7",
		"ansible_real_user_id": 2750,
		"ansible_virtualization_role": "guest",
		"ansible_distribution_file_variety": "RedHat",
		"ansible_env": {
			"_": "/usr/bin/python",
			"LESSOPEN": "||/usr/bin/lesspipe.sh %s",
			"SSH_CLIENT": "10.0.0.2 36518 22",
			"LOGNAME": "ansible",
			"USER": "ansible",
			"PATH": "/usr/lib64/qt-3.3/bin:/usr/dialogic/bin:/usr/local/bin:/usr/bin:/bin:/opt/wf/bin",
			"LANG": "en_US.UTF-8",
			"SHELL": "/bin/bash",
			"XDG_SESSION_ID": "197",
			"HOME": "/home/ansible",
			"XDG_RUNTIME_DIR": "/run/user/2750",
			"SSH_CONNECTION": "10.0.0.2 36518 10.1.0.2 22",
			"SHLVL": "2",
			"PWD": "/home/ansible",
			"MAIL": "/var/mail/ansible"
		}
	}
}
]`

JSON := []byte(input)
 
fmt.Printf("%s\n", JSON)
var d map[string]interface{}
json.Unmarshal(JSON, &d)

fmt.Printf("\n\n get result: \n\n")
//test_path_key := "vcenter_centos"
//fmt.Println(get(d, "data",test_path_key,"results",0,"instance"))

fmt.Println(get(d, 1,"mydata"))
}

目前,当我运行它时,在第14行(位于get函数内)发生恐慌:

get result:

panic: interface conversion: interface {} is map[string]interface {}, not []interface {}

goroutine 1 [running]: main.get(0x4d2f00, 0xc000056360, 0xc000075f68, 0x2, 0x2, 0x0, 0x0) C:/Users/eloy04580.TELETECH/go/src/process_json/process_json.go:14 +0x1e1 main.main() C:/Users/eloy04580.TELETECH/go/src/process_json/process_json.go:99 +0x21c exit status 2

我已经尝试了几种方法来处理这个问题,但似乎没有任何进展。任何帮助都将不胜感激。


更多关于Golang中动态JSON处理的求助指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

你好,康纳,

感谢你的帮助!啊,我明白了。我在那上面花了太多时间。现在看起来工作正常了。

凯文

更多关于Golang中动态JSON处理的求助指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


有一些第三方包可以用来扁平化 JSON 结构。或者,在处理嵌套和动态的 JSON 结构时,匿名结构体可能会是你的好帮手。请参阅此示例

我还有一个问题。我需要将这个结构体扁平化,以便最终能将其添加到数据库中。我想做的是遍历它,并构建一个不嵌套当前键的新结构,这样我就可以直接决定要将哪些值添加到数据库中,而无需在多层级结构中迂回操作。有什么想法吗?

谢谢。

你好,

我无法为我的数据定义一个静态结构体。我不知道它会包含什么内容。我知道它是嵌套的,但不知道深度如何,也不知道键是什么。我有一个动态的接口设置。相关的数据在本帖开头。但我需要能够独立引用结构中的每个元素以添加到数据库中,所以仅仅转储整个结构是没有帮助的。

谢谢,

Kevin

嗨 Kevin!

这是一个 Go Playground 链接,里面包含了可以正常运行的你的代码:https://play.golang.org/p/NjHhT-WqOJm

我只是将你的 var d map[string]interface{} 改成了 var d []map[string]interface{},因为你使用的 JSON 是一个 JSON 数组。

然后,我将 switch 语句中 get 方法的第二个 case 改为了: m.([]map[string]interface{})[idx]

我相信现在它应该可以正常工作了。如果你需要我进一步解释我所做的修改,请告诉我。

你的代码在处理动态JSON时遇到了类型断言失败的问题。问题在于你的get函数假设传入的m参数要么是map[string]interface{}要么是[]interface{},但实际上JSON的根节点是一个数组,而你尝试用map[string]interface{}来解析它。

以下是修复后的代码:

package main

import (
	"encoding/json"
	"fmt"
)

func get(m interface{}, path ...interface{}) (interface{}, error) {
	var current interface{} = m
	
	for i, p := range path {
		switch idx := p.(type) {
		case string:
			// 尝试转换为map
			if m, ok := current.(map[string]interface{}); ok {
				current = m[idx]
			} else {
				return nil, fmt.Errorf("路径位置 %d: 期望 map[string]interface{}, 得到 %T", i, current)
			}
		case int:
			// 尝试转换为slice
			if s, ok := current.([]interface{}); ok {
				if idx >= 0 && idx < len(s) {
					current = s[idx]
				} else {
					return nil, fmt.Errorf("路径位置 %d: 索引 %d 超出范围 (长度: %d)", i, idx, len(s))
				}
			} else {
				return nil, fmt.Errorf("路径位置 %d: 期望 []interface{}, 得到 %T", i, current)
			}
		default:
			return nil, fmt.Errorf("路径位置 %d: 不支持的路径类型 %T", i, p)
		}
		
		// 检查是否为空
		if current == nil {
			return nil, fmt.Errorf("路径位置 %d: 路径不存在", i)
		}
	}
	
	return current, nil
}

func main() {
	input := `[{
	"_id": "ansible_facts_localhost",
	"data": {
		"ansible_iscsi_iqn": "",
		"vcenter_centos": {
			"msg": "All items completed",
			"changed": false,
			"results": [
				{
				"ansible_loop_var": "item",
				"instance": {
					"hw_name": "mytst01",
					"hw_power_status": "poweredOn",
					"snapshots": [],
					"guest_question": null,
					"hw_interfaces": [
						"eth0",
						"eth1"
					],
					"hw_guest_id": null,
					"current_snapshot": null
				}
				}
			]
		}
	}
},{
	"_id": "ansible_facts_test02",
	"data": {
		"ansible_processor_count": 2,
		"gather_subset": [
			"all"
		],
		"ansible_product_version": "None",
		"ansible_service_mgr": "systemd",
		"ansible_fibre_channel_wwn": [],
		"module_setup": true,
		"ansible_memtotal_mb": 7821,
		"ansible_hostnqn": "",
		"ansible_distribution_version": "7.7",
		"ansible_real_user_id": 2750,
		"ansible_virtualization_role": "guest",
		"ansible_distribution_file_variety": "RedHat",
		"ansible_env": {
			"_": "/usr/bin/python",
			"LESSOPEN": "||/usr/bin/lesspipe.sh %s",
			"SSH_CLIENT": "10.0.0.2 36518 22",
			"LOGNAME": "ansible",
			"USER": "ansible",
			"PATH": "/usr/lib64/qt-3.3/bin:/usr/dialogic/bin:/usr/local/bin:/usr/bin:/bin:/opt/wf/bin",
			"LANG": "en_US.UTF-8",
			"SHELL": "/bin/bash",
			"XDG_SESSION_ID": "197",
			"HOME": "/home/ansible",
			"XDG_RUNTIME_DIR": "/run/user/2750",
			"SSH_CONNECTION": "10.0.0.2 36518 10.1.0.2 22",
			"SHLVL": "2",
			"PWD": "/home/ansible",
			"MAIL": "/var/mail/ansible"
		}
	}
}]`

	// 使用 interface{} 来解析未知结构的JSON
	var data interface{}
	if err := json.Unmarshal([]byte(input), &data); err != nil {
		fmt.Printf("JSON解析错误: %v\n", err)
		return
	}

	fmt.Println("测试1: 获取第一个元素的_id")
	if result, err := get(data, 0, "_id"); err != nil {
		fmt.Printf("错误: %v\n", err)
	} else {
		fmt.Printf("结果: %v\n", result)
	}

	fmt.Println("\n测试2: 获取第一个元素的data.vcenter_centos.results[0].instance.hw_name")
	if result, err := get(data, 0, "data", "vcenter_centos", "results", 0, "instance", "hw_name"); err != nil {
		fmt.Printf("错误: %v\n", err)
	} else {
		fmt.Printf("结果: %v\n", result)
	}

	fmt.Println("\n测试3: 获取第二个元素的data.ansible_env.PATH")
	if result, err := get(data, 1, "data", "ansible_env", "PATH"); err != nil {
		fmt.Printf("错误: %v\n", err)
	} else {
		fmt.Printf("结果: %v\n", result)
	}

	fmt.Println("\n测试4: 获取第一个元素的data.vcenter_centos.results[0].instance.hw_interfaces[1]")
	if result, err := get(data, 0, "data", "vcenter_centos", "results", 0, "instance", "hw_interfaces", 1); err != nil {
		fmt.Printf("错误: %v\n", err)
	} else {
		fmt.Printf("结果: %v\n", result)
	}

	fmt.Println("\n测试5: 错误路径测试")
	if result, err := get(data, 0, "data", "vcenter_centos", "results", 10, "instance"); err != nil {
		fmt.Printf("错误: %v\n", err)
	} else {
		fmt.Printf("结果: %v\n", result)
	}
}

主要修改:

  1. 修复JSON解析:使用interface{}而不是map[string]interface{}来解析JSON,这样可以处理数组作为根节点的情况。

  2. 增强get函数

    • 添加了错误处理,返回(interface{}, error)而不是单一的interface{}
    • 添加了类型检查,使用类型断言前先检查类型
    • 添加了边界检查,防止数组越界
    • 添加了路径不存在的检查
  3. 改进类型断言

    // 原来的代码(有问题)
    m = m.(map[string]interface{})[idx]
    
    // 修复后的代码
    if m, ok := current.(map[string]interface{}); ok {
        current = m[idx]
    }
    
  4. 添加错误信息:为每种可能的错误情况提供清晰的错误信息,便于调试。

这个修复后的代码可以正确处理:

  • JSON根节点是数组的情况
  • 混合使用字符串键和整数索引的路径
  • 路径不存在或类型不匹配的情况
  • 数组越界的情况

运行输出示例:

测试1: 获取第一个元素的_id
结果: ansible_facts_localhost

测试2: 获取第一个元素的data.vcenter_centos.results[0].instance.hw_name
结果: mytst01

测试3: 获取第二个元素的data.ansible_env.PATH
结果: /usr/lib64/qt-3.3/bin:/usr/dialogic/bin:/usr/local/bin:/usr/bin:/bin:/opt/wf/bin

测试4: 获取第一个元素的data.vcenter_centos.results[0].instance.hw_interfaces[1]
结果: eth1

测试5: 错误路径测试
错误: 路径位置 4: 索引 10 超出范围 (长度: 1)
回到顶部