我有一个变量需要是 astring或map[string]string(将从 JSON 反序列化)。所以我将其声明为interface{}. 我如何检查该值是否为map[string]string?
这个问题How to check interface is a map[string]string in golang几乎回答了我的问题。但接受的答案仅在变量声明为map[string]stringnot 且变量为 的情况下才有效interface{}。
package main
import (
"fmt"
)
func main() {
var myMap interface{}
myMap = map[string]interface{}{
"foo": "bar",
}
_, ok := myMap.(map[string]string)
if !ok {
fmt.Println("This will be printed")
}
}
Run Code Online (Sandbox Code Playgroud)
请参阅https://play.golang.org/p/mA-CVk7bdb9
不过我可以使用两种类型断言。一张在地图上,一张在地图上的值。
package main
import (
"fmt"
)
func main() {
var myMap interface{}
myMap = map[string]interface{}{
"foo": "bar",
}
valueMap, ok := myMap.(map[string]interface{})
if !ok {
fmt.Println("will not be printed")
}
for _, v := range valueMap {
if _, ok := v.(string); !ok {
fmt.Println("will not be printed")
}
}
}
Run Code Online (Sandbox Code Playgroud)
请参阅https://play.golang.org/p/hCl8eBcKSqE
问:有更好的办法吗?
如果将变量声明为 type interface{},则它就是type interface{}。它永远不具有map[keytype]valuetype某种价值。但是类型变量interface{}可以保存具有其他具体类型的值。当它这样做时,它就会这样做\xe2\x80\x94,这就是它的全部。它仍然是type interface{},但它包含其他类型的值。
这里的关键区别在于interface{}变量是什么以及它包含什么。任何接口变量实际上都有两个槽:一个用于保存其中存储的类型,另一个用于保存其中存储的值。每当您\xe2\x80\x94或任何人\xe2\x80\x94为变量分配值时,编译器都会填充两个槽:类型(来自您使用的值的类型)和值(来自您使用的值) 。1nil如果接口变量在两个槽中 都有,则比较等于 nil ;这也是默认的零值。
因此,您的运行时测试:
\n\nvalueMap, ok := myMap.(map[string]interface{})\nRun Code Online (Sandbox Code Playgroud)\n\n是明智的做法:如果myMap持有一个类型为 的值map[string]interface,ok则将其设置为true并valueMap包含该值(具有该类型)。如果myMap持有某个其他类型的值,ok则设置为false并valueMap设置为 类型的零值map[string]interface{}。换句话说,在运行时,代码首先检查类型槽,然后将值槽复制到valueMap并设置ok为 true,或者设置valueMap为nil并设置ok为 false。
如果且当ok已设置为时true,每个valueMap[k]值都是type interface{}。和以前一样,对于myMap其本身,每个interface{}变量都可以 \xe2\x80\x94 但不必 \xe2\x80\x94 保存类型 的值string,并且您必须使用某种“实际类型和值是什么”运行时测试来区分它们。
当您将json.Unmarshal解码后的 JSON 填充到 类型的变量中时interface{},它能够反序列化任何这些记录的 JSON 类型。然后该列表告诉您什么类型被填充到接口变量中:
\n\n\nRun Code Online (Sandbox Code Playgroud)\nbool, for JSON booleans\nfloat64, for JSON numbers\nstring, for JSON strings\n[]interface{}, for JSON arrays\nmap[string]interface{}, for JSON objects\nnil for JSON null\n
因此,在执行json.Unmarshal完类型为 的变量之后interface{},您应该检查变量的类型槽中放入的类型。您可以使用断言和ok布尔值来完成此操作,或者如果您愿意,也可以使用类型开关对其进行解码:
var i interface\nif err := json.Unmarshal(data, &i); err != nil {\n panic(err)\n}\nswitch v := i.(type) {\ncase string:\n ... code ...\ncase map[string]interface{}:\n ... code ...\n... add some or all of the types listed ...\n}\nRun Code Online (Sandbox Code Playgroud)\n\n问题是,无论您在此处的代码中执行什么操作,您都确实已将某些json.Unmarshal内容放入interface{}, 并且interface{} 是的类型i。您必须在运行时测试接口保存的类型和值对。
您的另一个选择是手动检查 JSON 字符串并决定提供给 的变量类型json.Unmarshal。这使得您可以在后面编写更少的代码,但在 之前Unmarshal编写更多代码。
在 Go Playground 上有一个更完整的示例,使用类型开关来检查json.Unmarshal. 它故意不完整,但我认为,有足够的输入和输出案例,可以让您了解如何处理所有内容,考虑到上面关于写入json.Unmarshal类型变量的引用interface{}。
1当然,如果您interface{}从其他地方分配一个interface{}:
var i1, i2 interface{}\n... set i1 from some actual value ...\n// more code, then:\ni2 = i1\nRun Code Online (Sandbox Code Playgroud)\n\n编译器只是将两个槽从 复制i1到i2。当你这样做时,两个独立插槽的事情就会变得更加清晰:
var f float64\n... code that sets f to, say, 1.5 ...\ni2 = f\nRun Code Online (Sandbox Code Playgroud)\n\n例如,当写入float64类型槽时,将值1.5写入值槽中。编译器知道这f一点,float64因此类型设置只是意味着“在其中粘贴一个常量”。编译器不一定知道的值f,因此值设置是实际值的副本。