dongyao2129 2016-03-30 13:37
浏览 31
已采纳

使用反射识别非内置类型

I need to differentiate such types as

type A []byte

from a []byte. Using reflect, reflect.TypeOf(A{}).Kind tells me that it is a Slice of byte. How can I differentiate []byte{} from A{}, without having a bounded list of types to check for?

Are there new ways to do it in newer versions of Go?

  • 写回答

1条回答 默认 最新

  • duanjing7298 2016-05-18 07:09
    关注

    Some background

    First let's clear some things related to types. Quoting from Spec: Types:

    A type determines the set of values and operations specific to values of that type. Types may be named or unnamed. Named types are specified by a (possibly qualified) type name; unnamed types are specified using a type literal, which composes a new type from existing types.

    So there are (predeclared) named types such as string, int etc, and you may also create new named types using type declarations (which involves the type keyword) such as type MyInt int. And there are unnamed types which are the result of a type literal (applied to / including named or unnamed types) such as []int, struct{i int}, *int etc.

    You can get the name of a named type using the Type.Name() method, which "returns an empty string for unnamed types":

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

    There are types which are predeclared and are ready for you to use them (either as-is, or in type literals):

    Named instances of the boolean, numeric, and string types are predeclared. Composite types—array, struct, pointer, function, interface, slice, map, and channel types—may be constructed using type literals.

    Predeclared types are:

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

    You may use Type.PkgPath() to get a named type's package path, which "if the type was predeclared (string, error) or unnamed (*T, struct{}, []int), the package path will be the empty string":

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

    So you have 2 tools available to you: Type.Name() to tell if the type is a named type, and Type.PkgPath() to tell if the type is not predeclared and is a named type.

    But care must be taken. If you use your own, named type in a type literal to construct a new type (e.g. []A), that will be an unnamed type (if you don't use the type keyword to construct a new, named type):

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

    What can you do in such cases? You may use Type.Elem() to get the type's element type, if type's Kind is Array, Chan, Map, Ptr, or Slice (else Type.Elem() panics):

    fmt.Printf("%q
    ", reflect.TypeOf([]A{}).Elem().Name())    // Element type: "A"
    fmt.Printf("%q
    ", reflect.TypeOf([]A{}).Elem().PkgPath()) // Which is named, so: "main"
    

    Summary

    Type.PkgPath() can be used to "filter out" predeclared and unnamed types. If PkgPath() returns a non-empty string, you can be sure it's a "custom" type. If it returns an empty string, it still may be an unnamed type (in which case Type.Name() returns "") constructed from a "custom" type; for that you may use Type.Elem() to see if it is constructed from a "custom" type, which may have to be applied recursively:

    // [][]A -> Elem() -> []A which is still unnamed: ""
    fmt.Printf("%q
    ", reflect.TypeOf([][]A{}).Elem().PkgPath())
    
    // [][]A -> Elem() -> []A -> Elem() -> A which is named: "main"
    fmt.Printf("%q
    ", reflect.TypeOf([][]A{}).Elem().Elem().PkgPath())
    

    Try all the examples on the Go Playground.

    Special case #1: Anonymous struct types

    There is also the case of an anonymous struct type which is unnamed, but it may have a field of a "custom" type. This case can be handled by iterating over the fields of the struct type and performing the same check on each field, and if any of them is found to be a "custom" type, we can claim the whole struct type to be "custom".

    Special case #2: Map types

    In case of maps we may consider an unnamed map type "custom" if any of its key or value type is "custom".

    The value type of a map can be queried with the above mentioned Type.Elem() method, and the key type of a map can be queried with the Type.Key() method - we also have to check this in case of maps.

    Example implementation

    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
    }
    

    Testing it (try it on the Go Playground):

    type K int
    var i int = 2
    fmt.Println(isCustom(reflect.TypeOf("")))                // false
    fmt.Println(isCustom(reflect.TypeOf(int(2))))            // false
    fmt.Println(isCustom(reflect.TypeOf([]int{})))           // false
    fmt.Println(isCustom(reflect.TypeOf(struct{ i int }{}))) // false
    fmt.Println(isCustom(reflect.TypeOf(&i)))                // false
    fmt.Println(isCustom(reflect.TypeOf(map[string]int{})))  // false
    fmt.Println(isCustom(reflect.TypeOf(A{})))               // true
    fmt.Println(isCustom(reflect.TypeOf(&A{})))              // true
    fmt.Println(isCustom(reflect.TypeOf([]A{})))             // true
    fmt.Println(isCustom(reflect.TypeOf([][]A{})))           // true
    fmt.Println(isCustom(reflect.TypeOf(struct{ a A }{})))   // true
    fmt.Println(isCustom(reflect.TypeOf(map[K]int{})))       // true
    fmt.Println(isCustom(reflect.TypeOf(map[string]K{})))    // true
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 oracle集群安装出bug
  • ¥15 关于#python#的问题:自动化测试
  • ¥20 问题请教!vue项目关于Nginx配置nonce安全策略的问题
  • ¥15 教务系统账号被盗号如何追溯设备
  • ¥20 delta降尺度方法,未来数据怎么降尺度
  • ¥15 c# 使用NPOI快速将datatable数据导入excel中指定sheet,要求快速高效
  • ¥15 再不同版本的系统上,TCP传输速度不一致
  • ¥15 高德地图点聚合中Marker的位置无法实时更新
  • ¥15 DIFY API Endpoint 问题。
  • ¥20 sub地址DHCP问题