如何通过引用设置 interface{} 参数?

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

How to set an interface{} parameter by reference?

问题

我有一个函数,它的参数类型是interface{}。这个参数表示我的模板数据。所以在每个页面上,它存储不同的数据类型(大多数是结构体)。我想要向这个参数的数据中追加一些数据,但是它是interface{}类型,我无法这样做。

这是我尝试过的代码:

  1. func LoadTemplate(templateData interface{}) {
  2. appendCustomData(&templateData)
  3. ... //其他不相关的功能
  4. }
  5. func appendCustomData(dst interface{}) {
  6. // ValueOf 进入反射领域
  7. dstPtrValue := reflect.ValueOf(dst)
  8. // 需要类型来创建值
  9. dstPtrType := dstPtrValue.Type()
  10. // *T -> T,如果不是指针则会崩溃
  11. dstType := dstPtrType.Elem()
  12. // *dst 中的 dst
  13. dstValue := reflect.Indirect(dstPtrValue)
  14. // *dst 中的零值
  15. zeroValue := reflect.Zero(dstType)
  16. // *dst = 0
  17. v := reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS")
  18. if v.IsValid() {
  19. v = reflect.ValueOf("new header css value")
  20. }
  21. reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))
  22. //dstValue.Set(zeroValue)
  23. fmt.Println("new dstValue: ", dstValue)
  24. }

我可以成功获取"HeaderCSS"的值。但是我无法用另一个值替换它。我做错了什么?

我的templateData看起来像这样:

我有一个通用的结构体:

  1. type TemplateData struct {
  2. FooterJS template.HTML
  3. HeaderJS template.HTML
  4. HeaderCSS template.HTML
  5. //还有一些其他字段
  6. }

我还有另一个结构体,如下所示:

  1. type pageStruct struct {
  2. TemplateData //继承前一个结构体
  3. Form template.HTML
  4. //还有一些其他的映射/字符串
  5. }

我将这个第二个结构体作为templateData参数发送。

现在我得到了这个错误:

在这一行出现了"reflect.Value.Set using unaddressable value"的错误:reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))

上面的代码受到了这个答案的启发:https://stackoverflow.com/a/26824071/1564840

我希望能够追加/编辑这个接口中的值。有什么办法可以做到吗?谢谢。

英文:

I have a function that has a parameter with the type interface{}. This parameter represents my template data. So on each page it stores different data types (mostly structs). I want to append some data to this parameter's data, but it's an interface{} type and I can't do it.

This is what I tried:

  1. func LoadTemplate(templateData interface) {
  2. appendCustomData(&templateData)
  3. ... //other functionality that is not relevant
  4. }
  5. func appendCustomData(dst interface{}) {
  6. // ValueOf to enter reflect-land
  7. dstPtrValue := reflect.ValueOf(dst)
  8. // need the type to create a value
  9. dstPtrType := dstPtrValue.Type()
  10. // *T -> T, crashes if not a ptr
  11. dstType := dstPtrType.Elem()
  12. // the *dst in *dst = zero
  13. dstValue := reflect.Indirect(dstPtrValue)
  14. // the zero in *dst = zero
  15. zeroValue := reflect.Zero(dstType)
  16. // the = in *dst = 0
  17. v := reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS")
  18. if v.IsValid() {
  19. v = reflect.ValueOf("new header css value")
  20. }
  21. reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))
  22. //dstValue.Set(zeroValue)
  23. fmt.Println("new dstValue: ", dstValue)
  24. }

I can successfully get the "HeaderCSS" value. But I can't replace it with another value. What am I doing wrong?

My templateData looks like this:

I have a generic struct:

  1. type TemplateData struct {
  2. FooterJS template.HTML
  3. HeaderJS template.HTML
  4. HeaderCSS template.HTML
  5. //and some more
  6. }

and I have another struct, such as:

  1. type pageStruct struct {
  2. TemplateData //extends the previous struct
  3. Form template.HTML
  4. // and some other maps/string
  5. }

I send this second struct as templateData argument.

Right now I get this error:

> "reflect.Value.Set using unaddressable value" at the line: reflect.ValueOf(dst).Elem().Elem().FieldByName("HeaderCSS").Set(reflect.ValueOf(v))

The code from above is inspired from this answer: https://stackoverflow.com/a/26824071/1564840

I want to be able to append/edit values from this interface. Any idea how can I do it? Thanks.

答案1

得分: 2

不要将指针传递给接口。相反,interface{}值应该包含指针。然后只需传递这个interface{}值:

  1. func LoadTemplate(templateData interface{}) {
  2. appendCustomData(templateData)
  3. // 其他不相关的功能
  4. }

即使你不能使用比interface{}更具体的类型(因为你必须允许多种类型),你仍然可以使用类型断言,它非常简单:

  1. func appendCustomData(d interface{}) {
  2. if ps, ok := d.(*pageStruct); ok {
  3. ps.TemplateData.HeaderCSS += "+new"
  4. }
  5. }

Go Playground上尝试一下。

如果你必须或者想要使用反射,可以这样实现appendCustomData()

  1. type Data struct {
  2. Name string
  3. Age int
  4. Marks []int
  5. }
  6. func appendCustomData(d interface{}) {
  7. v := reflect.ValueOf(d).Elem()
  8. if f := v.FieldByName("Name"); f.IsValid() {
  9. f.SetString(f.Interface().(string) + "2")
  10. }
  11. if f := v.FieldByName("Age"); f.IsValid() {
  12. f.SetInt(f.Int() + 2)
  13. }
  14. if f := v.FieldByName("Marks"); f.IsValid() {
  15. f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
  16. }
  17. if f := v.FieldByName("Invalid"); f.IsValid() {
  18. f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
  19. }
  20. }

测试一下:

  1. d := &Data{
  2. Name: "Bob",
  3. Age: 22,
  4. Marks: []int{5, 4, 3},
  5. }
  6. fmt.Printf("%+v\n", d)
  7. appendCustomData(d)
  8. fmt.Printf("%+v\n", d)

输出结果:

  1. &{Name:Bob Age:22 Marks:[5 4 3]}
  2. &{Name:Bob2 Age:24 Marks:[5 4 3 2]}

更新:

回答你修改后的问题:当传递的值是嵌入另一个结构体的结构体时,没有任何区别。但是interface{}中包装的值仍然必须是指针。

以下是一个示例,appendCustomData()pageStruct.TemplateData.HeaderCSS追加内容:

  1. func appendCustomData(d interface{}) {
  2. v := reflect.ValueOf(d).Elem()
  3. if f := v.FieldByName("TemplateData"); f.IsValid() {
  4. if f = f.FieldByName("HeaderCSS"); f.IsValid() {
  5. f.Set(reflect.ValueOf(f.Interface().(template.HTML) + "+new"))
  6. }
  7. }
  8. }

测试一下:

  1. ps := &pageStruct{
  2. TemplateData: TemplateData{
  3. HeaderCSS: template.HTML("old"),
  4. },
  5. }
  6. fmt.Printf("%+v\n", ps)
  7. appendCustomData(ps)
  8. fmt.Printf("%+v\n", ps)

输出结果:

  1. &{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old} Form:}
  2. &{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old+new} Form:}

你可以在Go Playground上尝试一下。

英文:

Don't pass a pointer to interface. Instead the interface{} value should contain the pointer. And simply just hand over this interface{} value:

  1. func LoadTemplate(templateData interface) {
  2. appendCustomData(templateData)
  3. ... //other functionality that is not relevant
  4. }

Even if you can't use a more concrete type than interface{} (because you must allow multiple types), you can still use type assertion, it will be "super" easy:

  1. func appendCustomData(d interface{}) {
  2. if ps, ok := d.(*pageStruct); ok {
  3. ps.TemplateData.HeaderCSS += "+new"
  4. }
  5. }

Try this one on the Go Playground.

If you must or want to use reflection, this is how appendCustomData() can be implemented:

  1. type Data struct {
  2. Name string
  3. Age int
  4. Marks []int
  5. }
  6. func appendCustomData(d interface{}) {
  7. v := reflect.ValueOf(d).Elem()
  8. if f := v.FieldByName("Name"); f.IsValid() {
  9. f.SetString(f.Interface().(string) + "2")
  10. }
  11. if f := v.FieldByName("Age"); f.IsValid() {
  12. f.SetInt(f.Int() + 2)
  13. }
  14. if f := v.FieldByName("Marks"); f.IsValid() {
  15. f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
  16. }
  17. if f := v.FieldByName("Invalid"); f.IsValid() {
  18. f.Set(reflect.ValueOf(append(f.Interface().([]int), 2)))
  19. }
  20. }

Testing it:

  1. d := &Data{
  2. Name: "Bob",
  3. Age: 22,
  4. Marks: []int{5, 4, 3},
  5. }
  6. fmt.Printf("%+v\n", d)
  7. appendCustomData(d)
  8. fmt.Printf("%+v\n", d)

Output (try it on the Go Playground):

  1. &{Name:Bob Age:22 Marks:[5 4 3]}
  2. &{Name:Bob2 Age:24 Marks:[5 4 3 2]}

Update:

To answer your edited question: there is no difference when the value passed is a struct that embeds another struct. But the value wrapped in the interface{} still must be a pointer.

Example appendCustomData() that appends content to pageStruct.TemplateData.HeaderCSS:

  1. func appendCustomData(d interface{}) {
  2. v := reflect.ValueOf(d).Elem()
  3. if f := v.FieldByName("TemplateData"); f.IsValid() {
  4. if f = f.FieldByName("HeaderCSS"); f.IsValid() {
  5. f.Set(reflect.ValueOf(f.Interface().(template.HTML) + "+new"))
  6. }
  7. }
  8. }

Testing it:

  1. ps := &pageStruct{
  2. TemplateData: TemplateData{
  3. HeaderCSS: template.HTML("old"),
  4. },
  5. }
  6. fmt.Printf("%+v\n", ps)
  7. appendCustomData(ps)
  8. fmt.Printf("%+v\n", ps)

Output (try it on the Go Playground):

  1. &{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old} Form:}
  2. &{TemplateData:{FooterJS: HeaderJS: HeaderCSS:old+new} Form:}

huangapple
  • 本文由 发表于 2017年7月4日 19:56:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/44905386.html
匿名

发表评论

匿名网友

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

确定