使用反射从映射中填充一个结构体。

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

Fill a struct from a map with reflection

问题

我有这样一张地图
type Store map[string]string

从这个地图中,我想要填充一个结构体。目标是,对于结构体的每个字段,找到与字段名匹配的键,取出值,并根据结构体字段的类型,将值转换为适当的结构体字段类型并设置它。

基本上,地图将包含整数、布尔值、字符串和持续时间(以字符串形式表示),所以转换应该是简单的strconv.Atoi()time.ParseDuration()等等。

此外,我想使用结构体标签来指定地图中的键名,因为结构体字段很可能是驼峰命名法,而地图中的键名可能是这样的"example_key"。

有没有办法实现这个?我已经阅读了关于Go语言反射的内容,但对我来说仍然很难理解。我只需要一个解决这个问题的解释,然后我想我可以自己来实现。

谢谢。

英文:

I have a map of this kind
type Store map[string]string

From this map, i would like to fill a struct. The goal is, for each field of the struct, find the key that match the field name, take the value, and based on the type of the struct field, convert the value to the proper struct field type and set it.

Basically the map will contains integers, booleans, strings and durations as strings so the convestion should be simply strconv.atoi(), time.parseDuration() ...

Also, i would like to use the structs tags to specify the name of the key in the map, because the struct field will be likely camelCase, while the keys in the map will be like this "example_key"

Any idea of how doing this. I have read about golang reflection but it is still opaque for me. I would just need an explanation to approach the problem, and then i think i can handle the implementation by my own.

Thanks

答案1

得分: 4

为了避免在评论中进行一些扩展讨论,我将简单地给出一个答案,解释我会选择的两种方法。一般来说,我会避免使用反射方法。虽然有些情况下需要使用反射,但大多数情况下,更冗长、更直观、更简单的代码会更好,即使它可能多出几行代码,而且这两种方法之间存在一些重叠。

首先,从一个简单的例子开始。假设你有三种类型,GroceryStoreComputerStoreCornerStore。在每个类型所在的包中,通常会定义一些类似构造函数的方法(它们实际上不是构造函数,只是在包范围内实例化一个类型并返回它的方法,但目的相同)。所以一个例子可能是这样的:

func NewComputerStore(store Store) *ComputerStore {
    return &ComputerStore{
        Name:     store["Name"],
        Location: store["Location"],
        // 其他字段
    }
}

当然,上面的例子是完全不安全的。实际上,你需要使用 v, ok := myMap["key"] 语法,然后在 ok 的情况下进行赋值,如果不是的话可能需要进行一些组合操作,比如赋予默认值、记录错误和抛出错误。

现在,如果你想要一个通用的方法... 它通常会更像这样(注意这是未经测试的伪代码,更像是一个示例):

func (s *Store) ConvertToObject(obj interface{}) error {
    val := reflect.ValueOf(obj)
    for i := 0; i < val.NumField(); i++ {
        switch v := val.Field(i).(type) {
        case int64:
            val.Field(i).SetInt(strconv.Atoi(s[val.Field(i).Name]))
        }
    }
}

所以上面代码的基本思想是这样的。你从一个存储映射开始。调用者知道该映射应该是什么类型。他们调用这个方法,传递一个该类型的新实例。在方法中,我们并不关心类型是什么,我们关心的是它的字段的类型。所以我遍历传入的对象的所有字段。每个字段都经过类型切换(因为对于这个方法应该处理的结构体中存在的每种类型,我都需要进行从字符串到 X 的转换,或者至少有一个合理的基本情况),在每种类型的情况下,你可以使用字段名在 Store 映射中查找它的值,并将其从字符串转换为 X 进行赋值。

希望这样能让你理解。如果还不清楚这种方法,请继续提问。请记住,上面的代码不打算运行。此外,我忽略了所有的错误处理,你将需要大量的错误处理来使通用函数正常工作(如果转换失败怎么办?如果类型无法识别怎么办?还有其他可能的错误,这些错误不会常见)。

英文:

To avoid some extended discussion in comments I'm just going to make an answer explaining the two approaches I would choose from. Generally speaking, I would avoid the reflective method. There are cases where it is called for but most of the time the more verbose, literal, simple code will be better, even if it's a few more lines and there is some overlap between what the methods do.

So, starting with the simple example. Suppose you got 3 types, GroceryStore, ComputerStore, and CornerStore. In whatever package each type is defined in, it is fairly common to define some constructor like methods (they're not technically constructors, just package scoped methods that instantiate a type and return it but same purpose). So an example would be;

func NewComputerStore(store Store) *ComputerStore {
      return &amp;ComputerStore{
            Name: store[&quot;Name&quot;],
            Location: store[&quot;Location&quot;],
            //ect
      }
}

Of course the example above is completely unsafe. In reality you need to use the v, ok := myMape[&quot;key&quot;] syntax and then assign if ok and do whatever if not (probably some combination of assign default value, log error, and throw error).

Now if you want a generalized method... It typically look something more like this (note this is untested and is more pseudo code than a working example);

func (s *Store) ConvertToObject(obj interface{}) error {
    val := reflect.ValueOf(obj)
    for i := 0; i &lt; val.NumField(); i++ {
        switch v := val.Field(i).(type) {
        case int64:
            val.Field(i).SetInt(strconv.Atoi(s[val.Field(i).Name]))
        }
    }
}

So the basic idea of the code above is this. You're starting with a store map. The caller knows what type that map is supposed to be. They call this method passing a new instance of that type. In the method we don't really care what the type is, we care what the type of it's fields are. So I iterate all the fields on the object passed in. Each field is put through the type switch (since I'll need a string to X conversion for every type that is present in the structs this method should work with, or at least a decent base case), in the case for each type you can do assignment using the field name to look up it's value in the Store map and converting from string to X.

Hope that makes sense. I can follow up if it doesn't really clarify the approach. Remember the code above is not intended to run. Also, I've ignored all error handling which you will need lots of to make a generalized function work (what to do if conversion fails? What if type isn't recognized? What about all the other possible errors that won't be common place?).

huangapple
  • 本文由 发表于 2015年10月15日 06:25:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/33136669.html
匿名

发表评论

匿名网友

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

确定