如何在 Go 中递归遍历任意嵌套的 JSON 数据(无结构体定义)

1次阅读

如何在 Go 中递归遍历任意嵌套的 JSON 数据(无结构体定义)

本文介绍如何使用 map[String]interface{} 和类型断言,配合递归函数,高效、安全地遍历动态结构的多层嵌套 json,无需预定义 Struct,适用于配置解析、日志分析等场景。

go 中处理未知结构的 jsON(即“任意嵌套 json”)时,无法依赖固定 struct,而应采用 encoding/json 包提供的通用解码方式:将 JSON 解析为 map[string]Interface{}(顶层对象)和 []interface{}(数组),再通过类型断言(Type Assertion)类型开关(Type switch 识别并递归处理每种数据类型

核心思路是:对每个值进行运行时类型判断——若为 map[string]interface{},则递归遍历其键值对;若为 []interface{},则递归遍历其元素;其余基础类型(如 string、float64、boolnil)直接处理。这种模式天然支持任意深度嵌套,且代码简洁可维护。

以下为完整、健壮的实现示例:

package main  import (     "encoding/json"     "fmt" )  func main() {     var m map[string]interface{}     if err := json.Unmarshal([]byte(input), &m); err != nil {         panic(err)     }     parseMap(m, "") }  // parseMap 递归解析 map[string]interface{},prefix 用于打印缩进(可选) func parseMap(m map[string]interface{}, prefix string) {     for key, val := range m {         fullKey := prefix + key         switch v := val.(type) {         case map[string]interface{}:             fmt.Printf("%s: (object)n", fullKey)             parseMap(v, fullKey+".") // 进入下一层,添加点号分隔         case []interface{}:             fmt.Printf("%s: (array, %d items)n", fullKey, len(v))             parseArray(v, fullKey+".")         case string:             fmt.Printf("%s: %qn", fullKey, v)         case float64: // JSON 数字默认为 float64             fmt.Printf("%s: %gn", fullKey, v)         case bool:             fmt.Printf("%s: %tn", fullKey, v)         case nil:             fmt.Printf("%s: nulln", fullKey)         default:             fmt.Printf("%s: %v (type %T)n", fullKey, v, v)         }     } }  // parseArray 递归解析 []interface{} func parseArray(a []interface{}, prefix string) {     for i, val := range a {         indexKey := fmt.Sprintf("%s[%d]", prefix, i)         switch v := val.(type) {         case map[string]interface{}:             fmt.Printf("%s: (object)n", indexKey)             parseMap(v, indexKey+".")         case []interface{}:             fmt.Printf("%s: (array, %d items)n", indexKey, len(v))             parseArray(v, indexKey+".")         case string:             fmt.Printf("%s: %qn", indexKey, v)         case float64:             fmt.Printf("%s: %gn", indexKey, v)         case bool:             fmt.Printf("%s: %tn", indexKey, v)         case nil:             fmt.Printf("%s: nulln", indexKey)         default:             fmt.Printf("%s: %v (type %T)n", indexKey, v, v)         }     } }  const input = ` {   "outterJSON": {     "innerJSON1": {       "value1": 10,       "value2": 22,       "InnerInnerArray": ["test1", "test2"],       "InnerInnerJSONArray": [{"fld1": "val1"}, {"fld2": "val2"}]     },     "InnerJSON2": "NoneValue"   } }`

关键优势说明:

  • 完全动态:不依赖任何预定义 struct,适配任意 JSON Schema;
  • 类型安全:通过 switch v := val.(type) 精确识别 map、slice、基础类型,避免 panic;
  • 可扩展性强:轻松添加新类型处理逻辑(如 int, int64 —— 注意 JSON 解析后整数也转为 float64);
  • 调试友好:输出带路径前缀(如 outterJSON.innerJSON1.fld1),便于定位字段;
  • 生产就绪:已处理 nil、布尔、浮点数等边界情况,符合 JSON 规范。

⚠️ 注意事项:

  • Go 的 json.Unmarshal 将所有数字统一解析为 float64,如需严格整型,应在业务逻辑中显式转换(例如 int(v.(float64))),并注意精度风险;
  • 若 JSON 极深(>1000 层),需警惕递归溢出,此时可改用显式([]interface{} 模拟)实现迭代版本;
  • 对性能极致敏感场景(如高频日志解析),可考虑 github.com/tidwall/gjson(零内存分配、极速查询),但会牺牲遍历灵活性。

掌握此模式,你便拥有了处理任意 JSON 结构的“瑞士军刀”——无论是提取所有字符串字段、查找特定 key、统计嵌套层级,还是构建通用 JSON 转换器,都可在此基础上快速扩展。

text=ZqhQzanResources