用反射填充映射中的结构
Fill a struct from a map with reflection
我有一张这样的地图
type Store map[string]string
根据这张地图,我想填充一个结构。目标是,对于结构的每个字段,找到与字段名称匹配的键,取值,并根据结构字段的类型,将值转换为适当的结构字段类型并设置它。
基本上地图将包含整数、布尔值、字符串和持续时间作为字符串,因此转换应该只是 strconv.atoi()
、time.parseDuration()
...
此外,我想使用structs标签来指定映射中键的名称,因为结构字段可能是驼峰式,而映射中的键将是这样的"example_key"
知道如何做到这一点。我已经阅读了有关 golang 反射的信息,但它对我来说仍然是不透明的。我只需要一个解释来解决这个问题,然后我想我可以自己处理实现。
谢谢
为了避免在评论中进行一些冗长的讨论,我只想做出一个答案来解释我会选择的两种方法。一般来说,我会避免反射方法。在某些情况下会调用它,但大多数情况下,更冗长、更直接、更简单的代码会更好,即使它多了几行,并且这些方法的作用之间存在一些重叠。
所以,从简单的例子开始。假设您有 3 种类型,GroceryStore
、ComputerStore
和 CornerStore
。在定义每种类型的任何包中,定义一些构造函数(如方法)是相当普遍的(它们在技术上不是构造函数,只是封装作用域方法实例化一个类型和 return 它但目的相同)。所以一个例子是;
func NewComputerStore(store Store) *ComputerStore {
return &ComputerStore{
Name: store["Name"],
Location: store["Location"],
//ect
}
}
当然上面的例子是完全不安全的。实际上,您需要使用 v, ok := myMape["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.
希望这是有道理的。如果它没有真正阐明方法,我可以跟进。请记住,上面的代码并非旨在 运行。此外,我忽略了所有错误处理,您需要 lots 才能使通用函数正常工作(如果转换失败怎么办?如果无法识别类型怎么办?所有其他不常见的可能错误?)。
我有一张这样的地图
type Store map[string]string
根据这张地图,我想填充一个结构。目标是,对于结构的每个字段,找到与字段名称匹配的键,取值,并根据结构字段的类型,将值转换为适当的结构字段类型并设置它。
基本上地图将包含整数、布尔值、字符串和持续时间作为字符串,因此转换应该只是 strconv.atoi()
、time.parseDuration()
...
此外,我想使用structs标签来指定映射中键的名称,因为结构字段可能是驼峰式,而映射中的键将是这样的"example_key"
知道如何做到这一点。我已经阅读了有关 golang 反射的信息,但它对我来说仍然是不透明的。我只需要一个解释来解决这个问题,然后我想我可以自己处理实现。
谢谢
为了避免在评论中进行一些冗长的讨论,我只想做出一个答案来解释我会选择的两种方法。一般来说,我会避免反射方法。在某些情况下会调用它,但大多数情况下,更冗长、更直接、更简单的代码会更好,即使它多了几行,并且这些方法的作用之间存在一些重叠。
所以,从简单的例子开始。假设您有 3 种类型,GroceryStore
、ComputerStore
和 CornerStore
。在定义每种类型的任何包中,定义一些构造函数(如方法)是相当普遍的(它们在技术上不是构造函数,只是封装作用域方法实例化一个类型和 return 它但目的相同)。所以一个例子是;
func NewComputerStore(store Store) *ComputerStore {
return &ComputerStore{
Name: store["Name"],
Location: store["Location"],
//ect
}
}
当然上面的例子是完全不安全的。实际上,您需要使用 v, ok := myMape["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.
希望这是有道理的。如果它没有真正阐明方法,我可以跟进。请记住,上面的代码并非旨在 运行。此外,我忽略了所有错误处理,您需要 lots 才能使通用函数正常工作(如果转换失败怎么办?如果无法识别类型怎么办?所有其他不常见的可能错误?)。