嵌套结构的反思

38
给定一个如下的结构体:
type B struct {
    X string
    Y string
}

type D struct {
    B
    Z string
}

我想反思一下 D,并且获取到字段 X、Y、Z。
直觉上,在尝试解决问题之前,我本以为我能够通过反射遍历结构体 D 并获取所有字段(X、Y、Z),而不必处理 B。
但是正如你所看到的,我只能通过反射看到嵌套的结构体 B,而无法看到它的字段。

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

有没有办法在反射到D时使B完全透明?
为什么我想要这样做?
想象一个常见的结构(在这个例子中是B),它通过嵌入在多个其他结构中使用。使用反射,试图将D复制到一个不同包中的另一个类似结构中。用于复制的目标结构将所有属性平铺布置(没有嵌入)。因此,源结构与目标结构之间存在不匹配(嵌入与非嵌入),但所有平铺布置的属性都是相同的。我不想为每个结构创建自定义解决方案。
2个回答

41

你期望的“透明度”只是语法糖,与数据表示无关。如果您想要一个能够展平数据结构的函数,您需要自己编写。

例如(在 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
}

18
也许你应该在扁平化之前检查该字段是否嵌入,以便模拟 OP 期望的“透明度”?即,if v.Kind() == reflect.Struct && v.Anonymous{ - themihai

4
使用以下代码将所有推广字段名称作为映射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)
        }
    }
}

使用方法如下:

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

在游乐场上运行它

这个程序按照问题中的要求打印了X、Y和Z,但也打印了B,因为B也是一个字段名。

这个答案中的函数可以改进:

  • 不要在递归类型定义时崩溃。
  • 不要在层次结构中重复包含相同级别的名称。

encoding/json/encode.go 中的 typeField 函数处理了这两个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接