获取已知结构字段的名称

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

Obtaining the name of a known struct field

问题

我有一个表示数据库中对象的结构体,类似于:

type Object struct {
    Id string
    Field1 string
    Field2 int
}

我想要一个函数,每当字段被修改时,它会更新数据库中的特定字段,类似于以下代码:

func (self *Object) SetField1(value string) {
    self.Field1 = value
    database.Update(self.Id, "Field1", self.Field1) // 伪代码
}

有没有办法替换"Field1"这个硬编码的字符串,使得我的代码能够抵御结构体字段顺序和命名的未来更改?

我已经查看了reflect包,希望能够获取表示我正在处理的字段的StructField,但它似乎要求使用硬编码的字符串来指定字段的名称,或者使用字段在结构体中的索引(这可能会发生变化)。

英文:

I have a struct which represents an object in a database, something like:

type Object struct {
    Id string
    Field1 string
    Field2 int
}

And I'd like to have a function that updates the specific field in the database whenever the field is modified, something along these lines:

func (self *Object) SetField1(value string) {
    self.Field1 = value
    database.Update(self.Id, "Field1", self.Field1) // pseudocode
}

Is there a way to replace the "Field1" hard-coded string such that my code is resistant to future changes in the struct field ordering and naming?

I've poked around the reflect package, and it would be nice to be able to get the StructField that represents the field I'm working with, but it seems to require either the name of the field via hard-coded string, or the field's index in the struct (which is subject to change).

答案1

得分: 6

不在你所谈论的上下文中。它不作为参数传递,所以你需要其他的方式来指定要发送的结构体字段。这里的思维差距在于你试图将那个设置函数当作属性来处理,但实际上它不是;与其他语言中的属性不同,属性绑定到特定的字段,而你的SetField1方法绑定到整个结构体。那个方法可能会设置两个字段。

通常,如果你正在进行基于字段的反射操作,并且想要对字段进行动态操作,你应该使用结构体标签。例如,像这样:

type Object struct {
    Id string     `db:"id"`
    Field1 string `db:"field1"`
    Field2 int    `db:"field2"`
}

你可以像这样访问这些标签:

package main

import (
    "reflect"
    "fmt"
)

func main() {
    var x struct {
        MyField int `core:"required"`
    }
    t := reflect.TypeOf(x)

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Println(field.Name, ":", field.Tag.Get("core"))
    }
}

这并不是一个完整的解决方案,但是...以这种方式提问是不可能的。

英文:

not in the context that you're talking about. It's not passed in as a parameter, so you need some other way of specifying which of the struct's fields to be sent. The mental gap here is you're trying to treat that set function like it's a property when it's not; the key difference between a property, as seen in other languages, is that a property is bound to a specific field, whereas your SetField1 method is bound to the whole struct. That method might as well set two fields.

Generally if you're doing field-wise reflective stuff, and you want to do fancy dynamic stuff with fields, you want to use struct tags. e.g., like this:

type Object struct {
    Id string     `db:&quot;id&quot;`
    Field1 string `db:&quot;field1&quot;`
    Field2 int    `db:&quot;field2&quot;`
}

you can access those tags like-a-this:

package main

import (
    &quot;reflect&quot;
    &quot;fmt&quot;
)

func main() {
    var x struct {
        MyField int `core:&quot;required&quot;`
    }
    t := reflect.TypeOf(x)

    for i := 0; i &lt; t.NumField(); i++ {
        field := t.Field(i)
        fmt.Println(field.Name, &quot;:&quot;, field.Tag.Get(&quot;core&quot;))
    }
}

that's not really a full solution, but... the question in the way it's asked is impossible to do.

答案2

得分: 3

你可以通过添加一个类似以下的方法来验证你的字符串:

func (self *Object) verify(field string) string {
    if _, ok := reflect.TypeOf(*self).FieldByName(field); ok {
        return field
    }
    panic("Invalid field name")
}

然后在将字符串传递给数据库更新时使用它:

func (self *Object) SetField1(value string) {
    self.Field1 = value
    database.Update(self.Id, self.verify("Field1"), self.Field1) // 伪代码
}

但是我认为,如果你愿意使用反射,最好的方法是创建一个通用的setField方法,该方法接受字段作为字符串和值作为interface{},检查字段和值,设置值并更新数据库。

这样,所有操作都使用字符串完成,要么成功要么引发异常,而且你不需要记住使用.verify()方法。

类似这样:

func (self *Object) SetField(field string, value interface{}) {
    // 使用反射验证字段和值
    // 使用反射设置值
    database.Update(self.Id, field, self.Field1)
}

不过我认为这在未导出的字段上可能不起作用。

英文:

You could validate your string by adding a method something like this:

func (self *Object) verify(field string) string {
	if _, ok := reflect.TypeOf(*self).FieldByName(field); ok {
		return field
	}
	panic(&quot;Invalid field name&quot;)
}

And then use it when passing the string to the database update

func (self *Object) SetField1(value string) {
	self.Field1 = value
	database.Update(self.Id, self.verify(&quot;Field1&quot;), self.Field1) // pseudocode
}

But I would think that if you're willing to use reflection, that you'd be better off just making a generic setField method that accepts the field as a string, and the value as a interface{}, checks the field and value, sets the value and updates the database.

This way everything is done using the string, so it'll either work or panic, and you don't need to remember to use the .verify() method.

Somthing like:

func (self *Object) SetField(field string, value interface{}) {
    // verify field and value using reflection
    // set the value using reflection
    database.Update(self.Id, field, self.Field1)
    
}

Though I don't think this'll work on unexported fields.

huangapple
  • 本文由 发表于 2013年1月25日 10:18:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/14514312.html
匿名

发表评论

匿名网友

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

确定