像python操作attr一样在go里用reflect 操作field (标题与内容弱相关)

发布时间 2023-04-02 01:20:22作者: xiaotushaoxia

用go快两年了,其实是基本没怎么用过反射。主要是感觉对上层的使用来说没啥用。

之前模仿python的getattr和setattr简单写过GetField和SetField,写完简单测了一下就丢一边了也没大量用(因为没有需求啊)

func SetField(obj any, attr string, value any) error {
	_, field, err := checkObjAndGetField(obj, attr)
	if err != nil {
		return err
	}
	if !field.CanSet() {
		return fmt.Errorf("cannot set %s field value in obj %T", attr, obj)
	}

	val := reflect.ValueOf(value)
	if field.Type() != val.Type() {
		return fmt.Errorf("provided value type %T didn't match obj field type %s", value, field.Type())
	}
	field.Set(val)
	return nil
}
func GetField(obj any, attr string) (any, error) {
	_, field, err := checkObjAndGetField(obj, attr)
	if err != nil {
		return nil, err
	}
	return field.Interface(), nil
}
func checkObjAndGetField(obj any, attr string) (ov reflect.Value, field reflect.Value, err error) {
	if obj == nil {
		err = fmt.Errorf("field set/get on nil")
		return
	}
	k := reflect.TypeOf(obj).Kind()
	if k != reflect.Struct && k != reflect.Ptr {
		err = fmt.Errorf("cannot set/get field on a non-struct interface: %T", obj)
		return
	}
	ov = reflect.Indirect(reflect.ValueOf(obj))

	field = ov.FieldByName(attr)
	if !field.IsValid() {
		err = fmt.Errorf("no such field: %s in obj %T", attr, obj)
	}
	return
}

今天用SetField的时候发现出现了我意料外的错误。当然这个错误是应该发生的,也很合理,这个意料外只要是指这种情况没有在我考虑之内

type TaskState int
type Task struct {
	State TaskState
}

func TestSetField(t *testing.T) {
	a := &Task{}
	err := SetField(a, "State", 3)
	fmt.Println(err) // provided value type int didn't match obj field type TaskState
}

ok,开始处理这个问题(嗯,这里需要一些reflect的前置知识)

int和TaskState类型(Type)是不一样的,在go里面Type是无穷无尽的,不过反射里面还有个叫Kind的东西,这个Kind是有限的。所以int和TaskState的Kind是一样的,
所以我把

field.Type() != val.Type()改成了
field.Type().Kind() != val.Type().Kind()

结果发现在filed.Set的时候panic了,“panic: reflect.Set: value of type int is not assignable to type TaskState”

哦 所以这里我要做一下类型转换,所以要用到reflet.Value.Convert(reflet.Type)

然后想到如果Kind也不一样的话,Convert肯定会panic的(测试结果也是如此)
所以就有两个(我脑子里冒出来两个)选择了。1.先检查Kind,Kind相同才Convert 2. recover起来写个tryConvert。
嗯,写了个检查Kind的东西

	val := reflect.ValueOf(value)
	ft := field.Type()
	vt := val.Type()
	if ft.Kind() != vt.Kind() {
		return fmt.Errorf("provided value %v(type:%s, kind:%s) didn't match obj(%T) field %s (type:%s, kind:%s)",
			val, vt, vt.Kind(), obj, attr, ft, ft.Kind())
	}

写完发现不对了,Kind里面有map,但是都是map,map[int]int和map[int]string肯定不能转换。(emm 应该是也有办法拿到kv的type,但是这太麻烦了就不弄了)
好的。(好像)只能用2了。
emm用类型转换的话,就可能会发生float到int这种转换,如果喜欢这样的话,这算是一个意外之喜了,但是对类型严格的话,这样就不好了
所以最后写了两个

func SetField(obj any, attr string, value any) error {
	_, field, err := checkObjAndGetField(obj, attr)
	if err != nil {
		return err
	}
	if !field.CanSet() {
		return fmt.Errorf("cannot set %s field value in obj %T", attr, obj)
	}
	val := reflect.ValueOf(value)

	// 先尝试convert, convert失败就报错
	// 这样会导致字段是int但是set float也会成功,因为float是可以convert到int的。如果偏爱这个行为的话,用这个挺好的
	fuzzError := tryConvertAndSet(field, val)
	if fuzzError == nil {
		return nil
	}
	clearErr := checkKind(obj, attr, field, val)
	if clearErr != nil {
		return clearErr
	}
	return fuzzError
	// 最初的版本 类型别名之间不能Set 这不能接受
	//if field.Type() != val.Type() {
	//	return fmt.Errorf("provided value type %T didn't match obj field type %s", value, field.Type())
	//}
	//field.Set(val)
	//return nil
}

func SetFieldStrict(obj any, attr string, value any) error {
	_, field, err := checkObjAndGetField(obj, attr)
	if err != nil {
		return err
	}
	if !field.CanSet() {
		return fmt.Errorf("cannot set %s field value in obj %T", attr, obj)
	}
	val := reflect.ValueOf(value)
	// 1 先尝试检查Kind, Kind不一样肯定就不能接受Set 这样不会有"隐式"的类型转换
	err = checkKind(obj, attr, field, val)
	if err != nil {
		return err
	}
	return tryConvertAndSet(field, val)
}

func checkKind(obj any, attr string, field reflect.Value, val reflect.Value) error {
	vt := val.Type()
	ft := field.Type()
	if ft.Kind() != vt.Kind() {
		return fmt.Errorf("provided value %v(type:%s, kind:%s) didn't match obj(%T) field %s (type:%s, kind:%s)",
			val, vt, vt.Kind(), obj, attr, ft, ft.Kind())
	}
	return nil
}

func tryConvertAndSet(field reflect.Value, val reflect.Value) error {
	if converted, er := tryConvert(field.Type(), val); er != nil {
		return trySet(field, converted)
	}
	return fmt.Errorf("convert %s to %s error", val.Type(), field.Type())
}

// trySet 就是有点怕 其实这个应该是没有必要的
func trySet(field reflect.Value, v reflect.Value) (err error) {
	defer func() {
		if p := recover(); p != nil {
			err = nil
		} else {
			err = fmt.Errorf("%v", p)
		}
	}()
	field.Set(v)
	return
}

好的 这个就叫做hreflect了(hight reflect) 顺便放到github欢迎使用

go get github.com/xiaotushaoxia/hreflect

好晚了。最后一个疑问,有空再看

// reflect.ValueOf(value).Kind()
// 
// reflect.TypeOf(value).Kind()
// 这两个东西有区别吗?怎么感觉一样的啊