Dikshant Adhikari
Dikshant Adhikari

Reputation: 662

Extracting tags from deeply nested structs using reflection

I am trying to extract some tags from some deeply nested structs. The structs were generated from a protobuf message and contain a json tag.

I have pointer to a struct that may contain a struct with fields whose tags that I may want. I can iterate using the type to get the fields of a struct but when I encounter a field that is pointer how do I get its value and then recurse?

// 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)
        }
    }
} 

Upvotes: 0

Views: 1411

Answers (2)

leaf bebop
leaf bebop

Reputation: 8232

Both reflect.Type and reflect.Value has an Elem() methods. As per document,

Elem method of Type.

// 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. 

You can use Elem() method to get the content of the pointer and then use the content to recurse. However, given that your orignal API is func(interface{},string), you need to use Value.Elem().Interface() to get a meaningful interface{}. But instead of that, I suggest you change the API to accept reflect.Type - for this is most clear for tag extraction.

Example code:

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)
    }
}

Note this code is very simple - it does not process tags of structs in slices or maps.

Upvotes: 2

user4466350
user4466350

Reputation:

when I encounter a field that is pointer how do I get its value and then recurse?

when you encounter a pointer, over reflect.Value or reflect.Type, you got to take the Elem() to proceed forward.

reflect.ValueOf(new(string)).Elem() goes from the value *string to string

reflect.TypeOf(new(string)).Elem() goes from the type *string to string

Upvotes: 0

Related Questions