Golang中如何访问map[string]interface{}变量的子键值

Golang中如何访问map[string]interface{}变量的子键值

var x_inboundbody map[string]interface{}
_ = json.Unmarshal(Inboundbody, &x_inboundbody)

我有一个深度为3到4层的结构,类似于JSON结构(由于其动态特性,无法预先创建结构体),并且每条记录的结构都是动态变化的。

我知道在我的结构中,从根级别开始存在以下键:entities.overalScore.overallScore,我希望能访问到它。

我想提取如下所示的数值60。

有人能帮忙吗?

Screenshot 2023-10-04 at 20.59.29


更多关于Golang中如何访问map[string]interface{}变量的子键值的实战教程也可以访问 https://www.itying.com/category-94-b0.html

19 回复

我确实是……因为我正在将其保存到文件中。

G

更多关于Golang中如何访问map[string]interface{}变量的子键值的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


请检查你是否确实从你的HTTP调用中接收到了数据。

// 代码部分保持原样,不翻译

检查解析后的响应是否为空或存在? 或者只是先部分解析响应,以区分不同的响应?

…解决方案根据此处的建议编译…并通过向ChatGPT提出想法并进一步引导这些问题…

有趣 ;)

G

我也使用 Python,那边要简单得多 😉

目前这部分工作要转向 Python 还为时过早。

但也许将来某个时候会这么做。

G

也许在 https://goplay.tools/ 上为你的问题制作一个小的可运行的概念验证会更容易?你可以把有问题的 JSON 粘贴在那里,这样我们就可以一起尝试解析它了。

如原帖所述,

我无法为此定义一个结构体,因为响应过于动态。 我只展示了一个响应,但我有15个。它们都来自同一个调用,只有在我获取到并将其放入一个可引用的对象后,我才能弄清楚它是哪个有效载荷。

G

因此,我成功编译了一个超集结构体,它应该/将会涵盖各种消息/响应。

既然我已经让上面那个方法正常工作了,

接下来我将按照上面的方式(以及下面 @Dean_Davidson 的例子),通过类型转换到结构体来创建第二个方法。

表情符号

G

比较 map[string]interface{}struct 这两种 JSON API 方式是一个很好的练习。 但我认为,最终这完全取决于你对响应中变化的确切了解程度,这样你才能找到最佳位置来处理错误或决定消息其余部分的内容。

我理解这种挫败感,但我不太确定最终在 Python 或其他语言中这是否会更容易?困难的部分始终是错误处理。

如果我的数据结构更简单一些,我大概能想象出这如何运作。 如果有一个简单的函数,接收一个字节并返回一个可寻址的 JSON 对象,那就太好了,这样就可以像 a := jsonObjName["attribute"]["subAttribute"] 这样引用它。

我尤其这么想,因为这门语言是围绕 API 创建的,有时传入的结构可能并非 100% 已知,但已知某些属性总是存在的。

json.RawMessage 在这里可能是你的好帮手。看看这个例子,他们最初并不知道子类型会是什么:

favicon.ico

json package - encoding/json - Go Packages

Package json implements encoding and decoding of JSON as defined in RFC 7159.

稍微修改了我的 for 循环,采纳了建议的浮点数类型转换。

for _, value := range overallScores {
	if value != nil {
		f, ok := value.(float64) // 因为有 ok 值,所以这里不会 panic
		if ok {
			if f > float64(vScore) {
				vScore = f
			}
		}
	}
}

或者

for _, value := range overallScores {
	if value != nil {
		f, ok := value.(float64) // 因为有 ok 值,所以这里不会 panic
		if ok && f > float64(vScore){
			vScore = f
		}
	}
}

我简化了JSON,以便更容易满足你的基本要求。 运行示例: https://goplay.tools/snippet/RIjLk-G-2u8

提到的那个网站确实对生成结构体非常有帮助,但在这种情况下,我也可以清理结构体标签(json: ...),因为变量已经有了正确的名称。

func main() {

	type Rsp struct {
		ResponseBody struct {
			Entities []struct {
				OverallScore struct {
					OverallScore int
				}
			}
		}
	}

	var rsp Rsp

	json.Unmarshal([]byte(rawJsonString), &rsp)

	for _, v := range rsp.ResponseBody.Entities {
		fmt.Println(v.OverallScore.OverallScore)
	}

}

我不会建议你转向Python。我无法忍受它没有编译器来捕获错误。在我看来,编译器是最重要的重构工具。

但我相当确定你在Go中还没有达到最优形式。 首先,如果不检查ok就将interface{}转换为float,存在引发panic的风险。(你没有对vScore这样做。)

这段代码演示了你可以不使用ok进行转换,但如果转换失败,它会产生panic:

var i1 interface{} = 3.14

	fmt.Println(i1)

	f1, ok := i1.(float64)

	fmt.Println(ok)
	fmt.Println(f1)

	var i2 interface{} = "not a float"

	fmt.Println(i2)

	//var f2 float64

	f2, ok := i2.(float64) // this won't panic because there's an ok

	fmt.Println(ok)
	fmt.Println(f2)

	f3 := i2.(float64) // this can panic

	fmt.Println(f3)

我也刚刚了解到,当存在ok时,它不会引发panic。这很有趣。

解决方案虽然粗糙,但有效…

InboundRequest, _ := http.NewRequest("POST", vGeneral.Httpposturl, bytes.NewBuffer(InboundBytes))

InboundRequest.Header.Set("Content-Type", "application/json; charset=UTF-8")

InboundResponse, _ := client.Do(InboundRequest)
defer InboundResponse.Body.Close()

jsonDataOutboundResponsebody, _ = io.ReadAll(InboundResponse.Body)

var outboundbodyMap map[string]interface{}

_ = json.Unmarshal(jsonDataOutboundResponsebody, &outboundbodyMap)

// 访问嵌套数组
entities, ok := inboundbodyMap["entities"].([]interface{})
if !ok {
	fmt.Println("Error accessing entities")
	return
}

var vScore float64
vScore = 0.0
for _, entity := range entities {
	overallScoreMap, ok := entity.(map[string]interface{})
	if !ok {
		fmt.Println("Error accessing entity details")
		return
	}

	// 访问每种语言的详细信息映射
	overallScores, ok := overallScoreMap["overallScore"].(map[string]interface{})
	if !ok {
		fmt.Println("Error accessing language details")
		return
	}
	for _, value := range overallScores {
		if value != nil {
			if value.(float64) > float64(vScore) {
				vScore = value.(float64)
			}
		}
	}
}
fmt.Println(vScore)

最简单的方法是将你的JSON映射到一个Go结构体(使用JSON-to-Go: 即时将JSON转换为Go)。 否则,你必须注意,从map中获取的每个元素都是一个struct{},因此必须将其转换为适当的数据类型。代码可能类似于:

Inboundbody := []byte(json_string)
var x_inboundbody map[string]interface{}
_ = json.Unmarshal(Inboundbody, &x_inboundbody)
fmt.Println(x_inboundbody) // map[string]interface{}

responseBodyInterface := x_inboundbody["responseBody"]            // interface{}
responseBodyMap := responseBodyInterface.(map[string]interface{}) // map[string]interface{}
fmt.Printf("responseBodyMap :%T\n%v\n", responseBodyMap, responseBodyMap)

entitiesInterface := responseBodyMap["entities"]         // interface{}
entitiesSlice := entitiesInterface.([]interface{})       // []interface{}
entitiesMap := entitiesSlice[0].(map[string]interface{}) // map[string]interface{}
fmt.Printf("entitiesMap :%T\n%v\n", entitiesMap, entitiesMap)

overallScoreInterface := entitiesMap["overallScore"] // // interface{}
overallScoreMap := overallScoreInterface.(map[string]interface{})
fmt.Printf("overallScoreMap :%T\n%+v\n", overallScoreMap, overallScoreMap)
overallScore := overallScoreMap["overallScore"]
fmt.Printf("overallScore :%T\n%v\n", overallScore, overallScore)

请查看下面的错误信息……同时我也添加了 <code> 来显示 inboundBody 的来源。

InboundRequest, _ := http.NewRequest("POST", vGeneral.Httpposturl, bytes.NewBuffer(InboundBytes))

InboundRequest.Header.Set("Content-Type", "application/json; charset=UTF-8")

InboundResponse, _ := client.Do(InboundRequest)
defer InboundResponse.Body.Close()

Inboundbody, _ = io.ReadAll(InboundResponse.Body)

//Inboundbody := []byte(json_string)
var x_inboundbody map[string]interface{}
_ = json.Unmarshal(Inboundbody, &x_inboundbody)
fmt.Println(x_inboundbody) // map[string]interface{}

responseBodyInterface := x_inboundbody["responseBody"]            // interface{}
responseBodyMap := responseBodyInterface.(map[string]interface{}) // map[string]interface{}
fmt.Printf("responseBodyMap :%T\n%v\n", responseBodyMap, responseBodyMap)
 Access-Control-Allow-Origin:[*] Cache-Control:[no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0] Connection:[keep-alive] Content-Length:[8044] Content-Type:[application/json] Date:[Thu, 05 Oct 2023 06:29:12 GMT] Server:[webserver] X-Content-Type-Options:[nosniff] X-Xss-Protection:[1; mode=block]]
2023/10/05 08:29:11 INFO: Inbound response Status       : 200 OK
2023/10/05 08:29:11 INFO: 
panic: interface conversion: interface {} is nil, not map[string]interface {}

goroutine 1 [running]:
main.runLoader({0x16d8bb33e?, 0x14000134a70?})
        /Users/george/Desktop/ProjectsCommon/fs/fs_scenario_tester2.2/cmd/producer.go:1166 +0x771c
main.main()
        /Users/george/Desktop/ProjectsCommon/fs/fs_scenario_tester2.2/cmd/producer.go:1688 +0x90
exit status 2
g-mbp.local:/Users/george/Desktop/ProjectsCommon/fs/fs_scenario_tester2.2 > ```

在Go中访问多层嵌套的map[string]interface{}时,需要进行类型断言和逐层访问。以下是访问entities.overalScore.overallScore的示例代码:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    // 示例JSON数据
    jsonData := `{
        "entities": {
            "overalScore": {
                "overallScore": 60
            }
        }
    }`

    var x_inboundbody map[string]interface{}
    _ = json.Unmarshal([]byte(jsonData), &x_inboundbody)

    // 逐层访问嵌套的map
    if entities, ok := x_inboundbody["entities"].(map[string]interface{}); ok {
        if overalScore, ok := entities["overalScore"].(map[string]interface{}); ok {
            if overallScore, ok := overalScore["overallScore"].(float64); ok {
                fmt.Printf("overallScore: %v\n", overallScore) // 输出: overallScore: 60
            }
        }
    }
}

对于更通用的解决方案,可以创建一个辅助函数来处理动态深度的访问:

func getNestedValue(data map[string]interface{}, keys ...string) (interface{}, bool) {
    var current interface{} = data
    
    for _, key := range keys {
        if m, ok := current.(map[string]interface{}); ok {
            if val, exists := m[key]; exists {
                current = val
            } else {
                return nil, false
            }
        } else {
            return nil, false
        }
    }
    return current, true
}

// 使用示例
func main() {
    jsonData := `{
        "entities": {
            "overalScore": {
                "overallScore": 60,
                "details": {
                    "score": 85
                }
            }
        }
    }`

    var x_inboundbody map[string]interface{}
    _ = json.Unmarshal([]byte(jsonData), &x_inboundbody)

    // 访问多层嵌套的值
    if value, ok := getNestedValue(x_inboundbody, "entities", "overalScore", "overallScore"); ok {
        if score, ok := value.(float64); ok {
            fmt.Printf("Score: %v\n", score) // 输出: Score: 60
        }
    }

    // 访问更深层的值
    if value, ok := getNestedValue(x_inboundbody, "entities", "overalScore", "details", "score"); ok {
        if score, ok := value.(float64); ok {
            fmt.Printf("Detail Score: %v\n", score) // 输出: Detail Score: 85
        }
    }
}

如果JSON结构中有数组,需要额外处理:

func getNestedValueWithArrays(data interface{}, keys ...string) (interface{}, bool) {
    current := data
    
    for _, key := range keys {
        switch v := current.(type) {
        case map[string]interface{}:
            if val, exists := v[key]; exists {
                current = val
            } else {
                return nil, false
            }
        case []interface{}:
            // 这里可以添加数组索引的处理逻辑
            // 例如: 如果key是数字字符串,转换为索引
            return nil, false
        default:
            return nil, false
        }
    }
    return current, true
}

这些方法可以处理动态深度的JSON结构访问,通过类型断言确保类型安全。

回到顶部