按其值将任何结构字段名称转换为字符串

Posted

技术标签:

【中文标题】按其值将任何结构字段名称转换为字符串【英文标题】:Convert any struct field name to string by it's value 【发布时间】:2022-01-15 08:31:03 【问题描述】:

在 GO 中,我想创建类似 C++ 风格的枚举:ClassName::EnumName::EnumValue。

struct MyClass 
    enum class EnumName  Success, Error ;
;

GO变体:

package MyPackage

type enumValue struct  val int 


type knownValus struct 
    Success, Error enumValue


var EnumName = knownValus 
    Success: enumValue0,
    Error:   enumValue1,

我的 C++ 类中有很多枚举,保留这个枚举名称对我来说非常重要。当我输入枚举名称时,我想查看此特定枚举的所有可能已知值,以便能够选择正确的值。 还有一个好处:我们可以将这个枚举传递给一个函数:

func HandleSmth(v enumValue) 
MyPackage.HandleSmth(MyPackage.EnumName.Success)

这太不可思议了!我将无法使用其他数据类型调用我的函数!

那么 Enum 的风格是这样的:

const (
    Success = iota
    Error = iota
)

这很丑,因为我无法确定我的函数可以处理的正确值。

问题是:如何实现通用的 EnumToString 函数,让我可以将任何枚举从私有包转换为字符串?

我已经为特定类型的结构实现了这个,但我不能为一般...

package EnumToString
func Convert(result enumValue) string 
    possibleEnums := EnumName
    elems := reflect.ValueOf(&possibleEnums).Elem()
    if elems.NumField() == 0 
        panic("No fields found")
    

    GetUnexportedField := func(field reflect.Value) interface 
        return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface()
    
    typeOfT := elems.Type()

    for i := 0; i < elems.NumField(); i++ 
        field := elems.Field(i)
        valStruct := GetUnexportedField(field).(enumValue)
        val := GetUnexportedField(reflect.ValueOf(&valStruct).Elem().Field(0))

        switch val.(type) 
        case int:
            if val.(int) == GetUnexportedField(reflect.ValueOf(&result).Elem().Field(0)).(int) 
                return typeOfT.Field(i).Name
            
        
    

    return ""


fmt.printLn(EnumToString.Convert(MyPackage.EnumName.Success)) // Should print 'Success'
fmt.printLn(EnumToString.Convert(OtherPackage.OtherName.OtherVale)) // Should print 'OtherValue'

但我的方法只适用于一个特定的结构。

如何使它与任何结构一起工作?

【问题讨论】:

它不会打印字段名称“成功”或“错误” 正如我最近提到的,我不想使用这种语法,你读过我的问题吗? 【参考方案1】:

也许你可以尝试类似 go enums 风格的东西,像这样:

type WeekdayEnum int

const (
  Sunday WeekdayEnum = iota
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
)

参考书籍:“Go 编程语言”- Alan A. A. Donovan 和 Brian W. Kernighan

【讨论】:

【参考方案2】:

您可以通过定义仅用于该枚举的特殊数据类型来强制函数仅采用 Go 中的某个枚举类型。

与 C++ 不同,Go 没有枚举,只有常量,并且没有打印枚举值名称的方法,但是为给定的枚举添加相对简单。

不幸的是,像 C++ 一样,Go 不支持像 Java 那样将值限制为仅有效的枚举值(尽管如果你愿意,当然可以添加一个检查)。

你不应该尝试使用反射来尝试添加语言功能或模仿C++,这会让你不开心,最好只是接受Go在这方面的限制并使用你所拥有的语言。

所以要定义一个数据类型,你只需添加一个以 int 作为基本类型的新类型

// Special datatype based on int - you can also add functions to this
type Foo int

然后定义您的枚举值。如果使用 iota ,它只分配给第一个 0 值(而不是像上面一样)。如果您想分配明确的值,您可以这样做(为了清楚地显示长列表或特定的连续值,如错误代码)。在 Go 中假设零值是 None 或 Invalid 而不是 Success 会更惯用。

// Values for Foo datatype
const (
    FooInvalid = iota
    FooError
    FooSuccess
)

然后使用它们:

// Accepts one param which must be a Foo
func HandleFoo(f Foo) string 
    return fmt.Sprintf("%s",f)

如果你想把它作为一个字符串,你必须编写或生成你自己的函数

// String representation of Foo
func (f Foo) String() string 
    switch f 
    case FooSuccess:
        return "Foo is Good"
    case FooError:
        return "Foo failed"
    default:
        return "Invalid"
    

如果正确性非常重要,您还可以编写一个函数来强制检查 Foo 是否为有效值(Go 不提供此功能,我认为 C++ 也不提供此功能?)。

// Is this foo valid?
func (f Foo) Valid() bool 
    return f == FooSuccess || f == FooError

这是操场上的一个例子: https://go.dev/play/p/4qjUoIIIIhU

【讨论】:

以上是关于按其值将任何结构字段名称转换为字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何从另一个输入文本字段Javascript中输入的值将值设置为输入隐藏字段

如何将结构转换为具有完全相同字段名称和类型的另一个结构?

根据另一个字段的值将字符串附加到 varchar2 字段

sap表字段最多有几个

如何解压缩数据框列中存在的 json 的键,值将转换为键作为列,而使用 python 将其值转换为列?

通过字符串名称类型转换为特定的结构类型