使用反射更新结构字段

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

Updating struct field using reflection

问题

我正在实现一个实体的部分更新。

实体结构如下:

type Entity struct {
    Id       string `readonly:"true"`
    Spec     EntitySpec
    Status   EntityState
}

type EntitySpec struct {
    Version  *string `readonly:"true"`
    Users    []*User
    Field1   *InnerStruct1
    Field2   []*InnerStruct2
}

我试图使用反射递归地遍历Entity结构体的字段,并更新用户被允许更新的字段:

func method(existingEntity interface{}, newEntity interface{}) {
    entityType := reflect.TypeOf(existingEntity)
    logger.Debugf("type: %v", entityType)
    for i := 0; i < entityType.NumField(); i++ {
        value := entityType.Field(i)
        logger.Debugf("Name: %s", value.Name)

        tag := value.Tag
        logger.Debugf("tag: %s", tag)
        if tag.Get("readonly") == "true" {
            logger.Debugf("readonly, go to next one")
            continue
        }

        oldField := reflect.Indirect(reflect.ValueOf(existingEntity).Field(i))
        newField := reflect.Indirect(reflect.ValueOf(newEntity).FieldByName(value.Name))
        logger.Debugf("type: %v", value.Type.Kind())
        if value.Type.Kind() == reflect.Struct {
            logger.Debugf("value: %v", oldField)
            //struct, go into it
            method(oldField.Interface(), newField.Interface())
        } else {
            if oldField != newField && oldField.String() != newField.String() {
                logger.Debugf("Type of old field: %v", oldField.Type())
                logger.Debugf("Type of new field: %v", newField.Type())
                logger.Debugf("Update: %v \nTo %v", oldField, newField)
                oldField.Set(newField) //在这里我得到了异常
            } else {
                logger.Debugf("Field values are equal")
            }
        }
    }
}

但是当我尝试使用oldField.Set(newField)来赋予一个新值时,我得到了一个异常:

reflect: reflect.Value.Set using unaddressable value

我还尝试过reflect.ValueOf(existingEntity).Field(i).Set(reflect.ValueOf(newEntity).FieldByName(value.Name)),但是得到了相同的异常。

你能解释一下为什么会发生这种情况,以及如何修复吗?

英文:

I'm implementing partial update for an entity.

The entity struct looks like

type Entity struct {
	Id       string `readonly:&quot;true&quot;`
	Spec     EntitySpec
	Status   EntityState
}


type EntitySpec struct {
	Version  *string `readonly:&quot;true&quot;`
	Users    []*User
	Field1   *InnerStruct1
	Field2   []*InnerStruct2
}

and so on.

So I'm trying to use reflection to iterate over Entity struct fields recursively and update fields which user is allowed to update:

func method(existingEntity interface{}, newEntity interface{}) {
	entityType := reflect.TypeOf(existingEntity)
	logger.Debugf(&quot;type: %v&quot;, entityType)
	for i := 0; i &lt; entityType.NumField(); i++ {
		value := entityType.Field(i)
		logger.Debugf(&quot;Name: %s&quot;, value.Name)

		tag := value.Tag
		logger.Debugf(&quot;tag: %s&quot;, tag)
		if tag.Get(&quot;readonly&quot;) == &quot;true&quot; {
			logger.Debugf(&quot;readonly, go to next one&quot;)
			continue
		}

		oldField := reflect.Indirect(reflect.ValueOf(existingEntity).Field(i))
		newField := reflect.Indirect(reflect.ValueOf(newEntity).FieldByName(value.Name))
		logger.Debugf(&quot;type: %v&quot;, value.Type.Kind())
		if value.Type.Kind() == reflect.Struct {
			logger.Debugf(&quot;value: %v&quot;, oldField)
			//struct, go into it
			method(oldField.Interface(), newField.Interface())
		} else {
			if oldField != newField &amp;&amp; oldField.String() != newField.String() {
				logger.Debugf(&quot;Type of old field: %v&quot;, oldField.Type())
				logger.Debugf(&quot;Type of new field: %v&quot;, newField.Type())
				logger.Debugf(&quot;Update: %v \nTo %v&quot;, oldField, newField)
				oldField.Set(newField) //HERE I get the exception
			} else {
				logger.Debugf(&quot;Field values are equal&quot;)
			}
		}
	}
}

But when I'm trying to assign a new value with oldField.Set(newField), I get an exception:

reflect: reflect.Value.Set using unaddressable value

I've also tried reflect.ValueOf(existingEntity).Field(i).Set(reflect.ValueOf(newEntity).FieldByName(value.Name)) but got the same exception.

Could you explain to me why it happens and how to fix this?

答案1

得分: 1

将要翻译的内容翻译为中文如下:

将指向要更新的值的指针传递给函数。下面是更新后的函数,用于接受现有实体的指针:

func method(existingEntity interface{}, newEntity interface{}) {
    entityType := reflect.TypeOf(existingEntity).Elem()
    for i := 0; i < entityType.NumField(); i++ {
        value := entityType.Field(i)
        tag := value.Tag
        if tag.Get("readonly") == "true" {
            continue
        }
        oldField := reflect.ValueOf(existingEntity).Elem().Field(i)
        newField := reflect.ValueOf(newEntity).FieldByName(value.Name)
        if value.Type.Kind() == reflect.Struct {
            method(oldField.Addr().Interface(), newField.Interface())
        } else {
            oldField.Set(newField)
        }
    }
}

使用方法如下:

var a Example
b := Example{123, 456, Example2{789}}
method(&a, b)

在 playground 中运行

这个函数处理了问题中的特定情况。如果需要进行深度克隆,则解决方案会更加复杂。

英文:

Pass a pointer to the value you are updating. Here's the function updated to take a pointer for existingEntity:

func method(existingEntity interface{}, newEntity interface{}) {
  entityType := reflect.TypeOf(existingEntity).Elem()
  for i := 0; i &lt; entityType.NumField(); i++ {
	value := entityType.Field(i)
	tag := value.Tag
	if tag.Get(&quot;readonly&quot;) == &quot;true&quot; {
		continue
	}
	oldField := reflect.ValueOf(existingEntity).Elem().Field(i)
	newField := reflect.ValueOf(newEntity).FieldByName(value.Name)
	if value.Type.Kind() == reflect.Struct {
		method(oldField.Addr().Interface(), newField.Interface())
	} else {
		oldField.Set(newField)
	}
  }
}

Use it like this:

var a Example
b := Example{123, 456, Example2{789}}
method(&amp;a, b)

Run it in the playground

This handles the specific case in the question. The solution is more complicated if a deep clone is required.

huangapple
  • 本文由 发表于 2017年4月14日 01:04:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/43397975.html
匿名

发表评论

匿名网友

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

确定