使用reflect识别非内置类型

时间:2016-03-30 13:37:47

标签: go reflection go-reflect

我需要将这些类型区分为

type A []byte

来自[]byte。使用reflectreflect.TypeOf(A{}).Kind告诉我它是Slice byte。如果没有要检查的类型的有界列表,我如何区分[]byte{}A{}

是否有新方法可以在较新版本的Go中执行此操作?

1 个答案:

答案 0 :(得分:11)

一些背景

首先让我们清楚一些与类型有关的事情。引自Spec: Types:

  

类型确定特定于该类型值的值集和操作集。类型可以命名为未命名。命名类型由(可能qualifiedtype name指定;未命名的类型是使用类型文字指定的,它是从现有类型组成的新类型。

所以有(预先声明的)命名类型,例如stringint等,您也可以使用type declarations创建新的命名类型(涉及type关键字},例如type MyInt int。还有未命名类型,它们是类型文字(应用于/包括命名或未命名类型)的结果,例如[]intstruct{i int}*int等。

您可以使用Type.Name()方法获取命名类型的名称,"为未命名类型返回空字符串"

var i int = 2
fmt.Printf("%q\n", reflect.TypeOf("abc").Name())              // Named: "string"
fmt.Printf("%q\n", reflect.TypeOf(int(2)).Name())             // Named: "int"
fmt.Printf("%q\n", reflect.TypeOf([]int{}).Name())            // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(struct{ i int }{}).Name())  // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(&struct{ i int }{}).Name()) // Unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(&i).Name())                 // Unnamed: ""

有些预先声明的类型可供您使用( as-is 或类型文字):

  

布尔,数字和字符串类型的命名实例是predeclared。复合类型 - 数组,结构,指针,函数,接口,切片,映射和通道类型 - 可以使用类型文字构造。

预先声明的类型是:

bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr

您可以使用Type.PkgPath()获取命名的类型的包路径,如果类型是预先声明的,则" stringerror)或未命名(*Tstruct{}[]int),包裹路径为空字符串"

fmt.Printf("%q\n", reflect.TypeOf("abc").PkgPath())    // Predeclared: ""
fmt.Printf("%q\n", reflect.TypeOf(A{}).PkgPath())      // Named: "main"
fmt.Printf("%q\n", reflect.TypeOf([]byte{}).PkgPath()) // Unnamed: ""

因此,您可以使用2个工具:Type.Name()判断类型是名为类型,Type.PkgPath()判断类型是否不预先声明,并且是命名类型

但必须小心谨慎。如果您在类型文字中使用自己的命名类型来构造新类型(例如[]A),那么这将是一个未命名的类型(如果您不使用type关键字来构造一个新的命名类型):

type ASlice []A

fmt.Printf("%q\n", reflect.TypeOf([]A{}).PkgPath())    // Also unnamed: ""
fmt.Printf("%q\n", reflect.TypeOf(ASlice{}).PkgPath()) // Named: "main"

在这种情况下你能做什么?如果类型KindType.Elem()ArrayChan,{{}},则可以使用Map来获取类型的元素类型{1}}或Ptr(否则Slice恐慌):

Type.Elem()

摘要

fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().Name()) // Element type: "A" fmt.Printf("%q\n", reflect.TypeOf([]A{}).Elem().PkgPath()) // Which is named, so: "main" 可用于"过滤"预先声明和未命名的类型。如果Type.PkgPath()返回非空字符串,则可以确定它是" custom"类型。如果它返回一个空字符串,它仍然可能是一个未命名的类型(在这种情况下PkgPath()返回Type.Name())由" custom"类型;为此,您可以使用""来查看它是否是由" custom"类型,可能必须递归

Type.Elem()

尝试Go Playground上的所有示例。

特例#1:匿名结构类型

还有一个匿名结构类型的情况是未命名的,但它可能有一个" custom"类型。可以通过迭代结构类型的字段并对每个字段执行相同的检查来处理这种情况,并且如果发现它们中的任何一个是" custom"类型,我们可以声称整个结构类型是" custom"。

特例#2:地图类型

如果是地图,我们可能会考虑一个未命名的地图类型" custom"如果它的任何键或值类型是" custom"。

可以使用上面提到的// [][]A -> Elem() -> []A which is still unnamed: "" fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().PkgPath()) // [][]A -> Elem() -> []A -> Elem() -> A which is named: "main" fmt.Printf("%q\n", reflect.TypeOf([][]A{}).Elem().Elem().PkgPath()) 方法查询地图的值类型,并且可以使用Type.Elem()方法查询地图的键类型 - 我们还必须检查以防万一地图。

示例实现

Type.Key()

测试它(在Go Playground上尝试):

func isCustom(t reflect.Type) bool {
    if t.PkgPath() != "" {
        return true
    }

    if k := t.Kind(); k == reflect.Array || k == reflect.Chan || k == reflect.Map ||
        k == reflect.Ptr || k == reflect.Slice {
        return isCustom(t.Elem()) || k == reflect.Map && isCustom(t.Key())
    } else if k == reflect.Struct {
        for i := t.NumField() - 1; i >= 0; i-- {
            if isCustom(t.Field(i).Type) {
                return true
            }
        }
    }

    return false
}