【问题标题】:Extracting tags from deeply nested structs using reflection使用反射从深度嵌套的结构中提取标签
【发布时间】:2020-03-19 17:36:43
【问题描述】:

我正在尝试从一些深度嵌套的结构中提取一些标签。这些结构是从 protobuf 消息生成的,并包含一个 json 标记。

我有一个指向一个结构的指针,该结构可能包含一个结构,其中包含我可能想要的标签。我可以使用该类型进行迭代以获取结构的字段,但是当我遇到一个指针字段时,我如何获取它的值然后递归?

// Struct has hierarchy like this 
a := &somepb.UpdateRequest{
        Updates: []*somepb.UpdateRequest_Foo{
            &somepb.UpdateRequest_Foo{
                Id: 1,
                Foo: &somepb.FooInfo{
                    Metadata: &somepb.Metadata{
                        Name:        "Foo",
                        Description: "Bar",
                    },
                    Some:    "s",
                    Things:  "o",
                    Are:     "m",
                    Broken:  "e",
                    Here:    "p",
                },
            },
        },
 } 

// ParseStruct parses struct tags of given object
func ParseStruct(obj interface{}, tag string) {
    r := reflect.ValueOf(obj)
    if r.Kind() == reflect.Ptr {
        obj = r.Elem().Interface()
    }
    rv := reflect.TypeOf(obj)
    for i := 0; i < rv.NumField(); i++ {
        f := rv.Field(i)
        // This is to avoid getting tags for Metadata itself (from the struct above)
        // which is *Metadata, I want the tags for Name and Description
        // inside *Metadata instead
        if f.Type.Kind() == reflect.Ptr {
            value := f.Tag.Get(tag)
            if len(value) == 0 {
                continue
            }
            fmt.Println(value)
        }
    }
} 

【问题讨论】:

    标签: go struct reflection tags


    【解决方案1】:

    reflect.Type 和 reflect.Value 都有一个 Elem() 方法。根据document,

    Type的Elem方法。

    // Elem returns a type's element type.
    // It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
    Elem() Type
    

    Value.Elem:

    func (v Value) Elem() Value
    
    Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil. 
    

    您可以使用Elem() 方法获取指针的内容,然后使用该内容进行递归。但是,鉴于您的原始 API 是 func(interface{},string),您需要使用 Value.Elem().Interface() 来获得有意义的 interface{}。但是,我建议您更改 API 以接受 reflect.Type,而不是那样,因为这对于标签提取来说是最清楚的。

    示例代码:

    func ParseStruct(t reflect.Type, tag string) {
        if t.Kind() == reflect.Ptr {
            t = t.Elm()
        }
        if t.Kind() != reflect.Struct {
            return
        }
    
        for i := 0; i < t.NumField(); i++ {
            f := t.Field(i)
            value := f.Tag.Get(tag)
            ft := t.Type
            if ft.Kind() == reflect.Ptr {
                ft = ft.Elem()
            }
    
            // It seems that you don't want a tag from an struct field; only other fields' tags are needed
            if ft.Kind() != reflect.Struct {
                if len(value) != 0 {
                    fmt.Println(value)
                }
                continue
            }
    
            ParseStruct(ft,tag)
        }
    }
    

    注意这段代码非常简单——它不处理切片或映射中结构的标签。

    【讨论】:

    • 明白。在第 12 行中应该是 ft.Type,然后是第 14 行中的 ft = ft.Elem() 和第 25 行中的 ParseStruct(ft, tag)?
    • @dikshant 在第 12 行将是 ft.Kind()(不变),否则您是正确的。
    【解决方案2】:

    当我遇到一个指针字段时,我如何获取它的值和 然后递归?

    当您遇到指针时,在reflect.Value 或reflect.Type 上,您必须采取Elem() 继续前进。

    reflect.ValueOf(new(string)).Elem() 从值*string 变为string

    reflect.TypeOf(new(string)).Elem() 从*string 类型变为string

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-18
      • 2021-09-18
      • 2017-04-03
      • 1970-01-01
      • 2021-12-28
      • 2018-09-12
      • 2021-10-08
      • 1970-01-01
      相关资源
      最近更新 更多