golang – 嵌入结构体的反射

huangapple go评论91阅读模式
英文:

golang - reflection on embedded structs

问题

给定一个如下的结构体:

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复制到另一个不同包中的类似结构体中。用于复制的目标结构体将会将所有属性平铺布置(没有嵌入)。因此,源结构体与目标结构体之间存在不匹配(嵌入与非嵌入),但所有平铺布置的属性都是相同的。我不想为每个结构体创建自定义解决方案。

英文:

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.

答案1

得分: 40

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

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

play链接

英文:

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 &lt; 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
}

答案2

得分: 3

使用以下代码将所有提升的字段名作为键收集到映射m中:

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

    // 如果不是结构体或指向结构体的指针,则返回。
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }
    if t.Kind() != reflect.Struct {
        return
    }

    // 遍历字段,在映射中收集字段名。
    for i := 0; i < t.NumField(); i++ {
        sf := t.Field(i)
        m[sf.Name] = struct{}{}

        // 递归进入匿名字段。
        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)
}

在 playground 上运行

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

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

  • 不会在递归类型定义时出错。
  • 不会包含在层级结构中重复的名称。

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

英文:

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 &lt; 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.

huangapple
  • 本文由 发表于 2014年6月21日 02:26:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/24333494.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定