Go reflect 获取嵌套结构体字段

3,747 阅读2分钟

示例

有时obj的字段都是未导出,虽然可以查看源码知道obj的数据结构,但是无法直接访问指定字段的值,用反射可以达到目的

type A struct {
    num int64
    level string
    b *B
}

type B struct {
    value string
    c *C
}

type C struct {
    name string
    id int64
}

func getNestFile(i interface{}) {
    objA := reflect.ValueOf(i).Elem()
    objB := objA.Field(2).Elem()
    objC := objB.Field(1).Elem()
    idC := objC.Field(1).Int()
    fmt.Println(idC)
}

func main() {
    c := C{name: "namec", id: 100}
    b := B{value: "bvalue", c: &c}
    a := &A{num: 10, level: "high", b: &b}

    getNestFile(a) // 100
}

反射三定律

1. Reflection goes from interface value to reflection Object.
2. Reflection goes from refelction object to interface value.
3. To modify a reflection object, the value must be settable.

reflect.TypeOf

返回参数的动态类型

type CustomizeInt int

fmt.Println(reflect.TypeOf(1))				// int
fmt.Println(reflect.TypeOf(1).Kind())			// int
fmt.Println(reflect.TypeOf(CustomizeInt(1)))		// main.CustomizeInt
fmt.Println(reflect.TypeOf(CustomizeInt(1)).Kind())	// int

reflect.ValueOf

ValueOf源码

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
	if i == nil {
		return Value{}
	}

	// TODO: Maybe allow contents of a Value to live on the stack.
	// For now we make the contents always escape to the heap. It
	// makes life easier in a few places (see chanrecv/mapassign
	// comment below).
	escapes(i)

	return unpackEface(i)
}

这里意思是valueOf返回一个用参数i的值来初始化的新的Value,我们可以测试一下:

a := A{Num: 100}
b := reflect.ValueOf(a).Interface().(A)
b.Num = 1
fmt.Println(a) // {100  <nil>}
fmt.Println(b) // {1  <nil>}

依然是两个无任何关系的值,那如何才能修改对象a的字段呢?
我们一般会想到指针,所以这里试着传入a的地址,也就是&a

a := A{Num: 100}
fmt.Println(reflect.ValueOf(&a))          // &{100  <nil>}
fmt.Println(reflect.ValueOf(&a).Kind())   // ptr
reflect.ValueOf(&a).Field(0)              // panic: reflect: call of reflect.Value.Field on ptr Value

这样看来也不行,ValueOf仅仅是返回了一份拷贝过来的指针而已,不代表一个对象,所以不能调用Field函数, 那咋整,这里就要介绍下Elem函数
截取了下Elem函数的说明

// 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.
func (v Value) Elem() Value {
	k := v.kind()

the pointer v points to意思是返回指针指向的对象

a := A{Num: 100}
fmt.Println(reflect.ValueOf(&a).Elem().Field(0).CanSet())  // true
fmt.Println(reflect.ValueOf(&a).Elem().Type())             // min.A

但是当将Num字段改成非导出的num,就无法用这种方式修改值了

fmt.Println(reflect.ValueOf(&a).Elem().Field(0).CanSet()) // false

可以利用reflect.NewAt来完成修改非导出字段的目的

// NewAt returns a Value representing a pointer to a value of the
// specified type, using p as that pointer.
func NewAt(typ Type, p unsafe.Pointer) Value {
	fl := flag(Ptr)
	t := typ.(*rtype)
	return Value{t.ptrTo(), p, fl}
}
func setUnExportValue(ptr interface{}, fileName string, newValue interface{}) error {
    // 获得要修改的字段
    v := reflect.ValueOf(ptr).Elem().FieldByName(fileName)

    v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()

    nv := reflect.ValueOf(newValue)
    if v.Kind() != nv.Kind() {
            return fmt.Errorf("expected kind %v, got kind: %v", v.Kind(), nv.Kind())
    }
    v.Set(nv)
    return nil
}

func main() {
    a := A{num: 100}
    setUnExportValue(&a, "num", int64(1))
    fmt.Println(a) // {1  <nil>}
}