简体   繁体   中英

golang - reflection on embedded structs

Given a struct like so:

type B struct {
    X string
    Y string
}

type D struct {
    B
    Z string
}

I want to reflect on D and get to the fields X, Y, Z.

Intuitively, before attempting the solution, I was assuming I would be able to traverse the struct D and get all fields using reflection (X, Y, Z) and won't have to deal with B.

But as you can see, I only see the embedded struct B using reflection and not its fields.

http://play.golang.org/p/qZQD5GdTA8

Is there a way I can make B fully transparent when reflecting on D?

Why do I want this?

Imaging a common struct (B in the example here), that is used in multiple other structs by using embedding. Using reflection, the attempt is to copy D into another similar struct in a different package. The destination struct for copying will have all attributes flatly laid out (no embedding there). So there is a mismatch from the source to the destination (embedding vs no embedding) but all the attributes flatly laid out are the same. I don't want to create custom solutions for each struct.

The 'transparency' you expected is just syntactic sugar and has nothing to do with the data representation. If you want to have a function that flattens your data structure, you would have to write it by yourself.

For example ( On play ):

func DeepFields(iface interface{}) []reflect.Value {
    fields := make([]reflect.Value, 0)
    ifv := reflect.ValueOf(iface)
    ift := reflect.TypeOf(iface)

    for i := 0; i < ift.NumField(); i++ {
        v := ifv.Field(i)

        switch v.Kind() {
        case reflect.Struct:
            fields = append(fields, DeepFields(v.Interface())...)
        default:
            fields = append(fields, v)
        }
    }

    return fields
}

Use the following code to collect all promoted field names as keys in map m :

func collectFieldNames(t reflect.Type, m map[string]struct{}) {

    // Return if not struct or pointer to struct.
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }
    if t.Kind() != reflect.Struct {
        return
    }

    // Iterate through fields collecting names in map.
    for i := 0; i < t.NumField(); i++ {
        sf := t.Field(i)
        m[sf.Name] = struct{}{}

        // Recurse into anonymous fields.
        if sf.Anonymous {
            collectFieldNames(sf.Type, m)
        }
    }
}

Use it like this:

m := make(map[string]struct{})
collectFieldNames(reflect.TypeOf((*D)(nil)), m)
for name := range m {
    fmt.Println(name)
}

Run it on the playground .

This program prints X, Y an Z as requested in the question, but also B because B is also a field name.

This function in this answer can be improved:

  • Don't blow up on recursive type definitions.
  • Do not include names repeated at the same level in the hierarchy.

The typeField function in encoding/json/encode.go handles both of these issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM