可以使用typedef来进行类型断言吗?

huangapple go评论81阅读模式
英文:

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
}

huangapple
  • 本文由 发表于 2021年7月27日 19:09:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/68543788.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定