英文:
Can I make a type assertion work with a typedef?
问题
我有一个自定义类型,可以更方便地使用 API:
type Object map[string]interface{}
但是这个东西不起作用:
var mockResponse = map[string]interface{}{"success": true}
resp, ok := mockResponse.(Object)
// ok = false
我能做些什么让 mockResponse.(Object)
的 ok
变为 true
吗?它们基本上是相同的类型...
英文:
I have a custom type that allows me to work with an API more conveniently:
type Object map[string]interface{}
And this thing doesn't work:
var mockResponse = map[string]interface{}{"success": true}
resp, ok := mockResponse.(Object)
// ok = false
Can I do anything so mockResponse.(Object)
's ok
is true
? It's basically the same type...
答案1
得分: 7
首先,你的“伪代码”是一个编译时错误。你不能在具体类型的值上使用类型断言(没有意义,具体类型就是具体类型,没有其他),你只能在接口值上使用它。
示例应该是:
var mockResponse interface{} = map[string]interface{}{"success": true}
resp, ok := mockResponse.(Object)
请注意,如果mockResponse
的类型是map[string]interface{}
,那么就不需要类型断言,你可以简单地将其转换为Object
,如下所示:
var mockResponse = map[string]interface{}{"success": true}
obj := Object(mockResponse)
类型定义会创建一个新的、独立的类型。只有当你使用相同的具体类型时,才能进行到具体类型的类型断言。
你可以编写一个辅助函数,从接口值中提取Object
,它可以处理map[string]interface{}
,以及具体类型为Object
的情况:
func getObj(x interface{}) (o Object, ok bool) {
switch v := x.(type) {
case Object:
return v, true
case map[string]interface{}:
return Object(v), true
}
return nil, false
}
测试一下:
resp, ok := getObj(map[string]interface{}{"success": true})
fmt.Println(resp, ok)
resp, ok = getObj(Object{"success": true})
fmt.Println(resp, ok)
resp, ok = getObj("invalid")
fmt.Println(resp, ok)
输出结果(在Go Playground上尝试):
map[success:true] true
map[success:true] true
map[] false
注意:上述的getObj()
并没有处理所有可能的情况,其中一个Object
可能从接口值中“获取”的情况。
例如,如果我们有这样的类型定义:
type Object2 Object
将一个Object2
的值传递给getObj()
,将无法从中提取出Object
,即使Object2
的值可以转换为Object
:
resp, ok = getObj(Object2{"success": true})
fmt.Println(resp, ok) // map[] false
如果你想处理所有可能的情况,其中具体值可以转换为Object
,你可以使用反射:
var objType = reflect.TypeOf(Object{})
func getObj(x interface{}) (o Object, ok bool) {
if v := reflect.ValueOf(x); v.Type().ConvertibleTo(objType) {
o, ok = v.Convert(objType).Interface().(Object)
return
}
return nil, false
}
测试一下(在Go Playground上尝试):
resp, ok = getObj(Object2{"success": true})
fmt.Println(resp, ok) // map[success:true] true
使用反射比简单的类型断言或类型切换要慢,所以你可以混合使用这两种解决方案:
func getObj(x interface{}) (o Object, ok bool) {
// 首先尝试简单的情况:
switch v := x.(type) {
case Object:
return v, true
case map[string]interface{}:
return Object(v), true
}
// 然后退回到反射:
if v := reflect.ValueOf(x); v.Type().ConvertibleTo(objType) {
o, ok = v.Convert(objType).Interface().(Object)
return
}
return nil, false
}
英文:
First of all, your "pseudo code" is a compile time error. You can't use type assertion on a value with concrete type (there's no point, a concrete type is a concrete type and nothing else), you may only use it on an interface value.
The example should be:
var mockResponse interface{} = map[string]interface{}{"success": true}
resp, ok := mockResponse.(Object)
Note that if mockResponse
would be of type map[string]interface{}
, then no type assertion would be needed, you could simply convert it to Object
like this:
var mockResponse = map[string]interface{}{"success": true}
obj := Object(mockResponse)
The type definition creates a new, distinct type. And the type assertion to a concrete type only holds if you use the same concrete type.
What you may do is write a helper function which extracts Object
from an interface value, which may handle both map[string]interface{}
and also if the concrete type is Object
:
func getObj(x interface{}) (o Object, ok bool) {
switch v := x.(type) {
case Object:
return v, true
case map[string]interface{}:
return Object(v), true
}
return nil, false
}
Testing it:
resp, ok := getObj(map[string]interface{}{"success": true})
fmt.Println(resp, ok)
resp, ok = getObj(Object{"success": true})
fmt.Println(resp, ok)
resp, ok = getObj("invalid")
fmt.Println(resp, ok)
Output (try it on the Go Playground):
map[success:true] true
map[success:true] true
map[] false
Note: the above getObj()
does not handle all possible cases where an Object
could be "acquired" from the interface value.
For example if we'd have a type definition like this:
type Object2 Object
Passing a value of Object2
to getObj()
, an Object
would not be extracted from it, even though a value of type Object2
could be converted to Object
:
resp, ok = getObj(Object2{"success": true})
fmt.Println(resp, ok) // map[] false
If you want to handle all possible cases where the concrete value is convertible to Object
, you may use reflection:
var objType = reflect.TypeOf(Object{})
func getObj(x interface{}) (o Object, ok bool) {
if v := reflect.ValueOf(x); v.Type().ConvertibleTo(objType) {
o, ok = v.Convert(objType).Interface().(Object)
return
}
return nil, false
}
Testing it (try this one on the Go Playground):
resp, ok = getObj(Object2{"success": true})
fmt.Println(resp, ok) // map[success:true] true
Using reflection is slower than a simple type assertion or type switch, so you can mix the 2 solutions:
func getObj(x interface{}) (o Object, ok bool) {
// First try the trivial cases:
switch v := x.(type) {
case Object:
return v, true
case map[string]interface{}:
return Object(v), true
}
// Then revert to reflection:
if v := reflect.ValueOf(x); v.Type().ConvertibleTo(objType) {
o, ok = v.Convert(objType).Interface().(Object)
return
}
return nil, false
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论