Home > database >  Convert any struct field name to string by it's value
Convert any struct field name to string by it's value

Time:12-11

In GO I want to create enum like in C style: ClassName::EnumName::EnumValue.

struct MyClass {
    enum class EnumName { Success, Error };
};

GO variant:

package MyPackage

type enumValue struct { val int }


type knownValus struct {
    Success, Error enumValue
}

var EnumName = knownValus {
    Success: enumValue{0},
    Error:   enumValue{1},
}

I have a lot of enums in my C class it is very important for me to keep this enum name. When I type enum name I want to see all the possible known values for this specific enum to be able to choose the proper one. One more advantage: we can pass this enum to a function:

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

This is incredible! I will not be able to call my function with a different data type!

And what about Enum in style like this:

const (
    Success = iota
    Error = iota
)

It's pretty ugly because I cannot figure out the proper values that my function can handle.

The question is: how to implement general EnumToString function that will allow me to convert any enum from a private packages to a string?

I've implemented this for a specific type struct, but I cannot for a general...

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'

But my approach works for the only one specific struct.

How to make it working with any structs?

CodePudding user response:

Maybe you can try something like go enums style, like this:

type WeekdayEnum int

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

Book reference: "The Go Programing Language" - Alan A. A. Donovan & Brian W. Kernighan

CodePudding user response:

You can enforce a function taking only a certain enum type in Go by defining a special datatype used only for that enum.

Unlike C Go does not have enums, only constants, and does not have a way to print an enum value name, but this is relatively simple to add for a given enum.

Like C Go unfortunately does not support limiting the values to only valid enum values as Java does (though you can of course add a check for that if you wish).

You should not attempt to use reflect to attempt to add language features or imitate C , this will make you unhappy, better to just accept the limitations of Go in this regard and work with the language you have.

So to define a datatype you'd just add a new type with int as the base type

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

Then define your enum values. If using iota is it only assigned to the first 0 value (not both as you have it above). If you want to assign explicit values you can do so (for clarity in long lists or specific contiguous values like error codes). It would be more idiomatic in Go to assume the zero value is None or Invalid rather than Success.

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

Then use them:

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

Then if you want this as a string you'd have to write or generate your own function

// 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"
    }
}

If correctness is very important, you could also write a function to enforce validity which checks the Foo is a valid value (Go doesn't provide this, and I don't think C does either?).

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

Here is an example on the playground: https://go.dev/play/p/4qjUoIIIIhU

  • Related