Search code examples
goprotocol-buffersgrpc-go

How to get a Protobuf enum value from its string representation?


I can get the string value of a Protobuf enum with this instruction:

str := testPB.Status_ENABLED.String()

How can I perform the inverse operation? (from a string, get the enum element).


Solution

  • The generated code has a map called <EnumName>_value of type map[string]int32. Then you can convert the numerical value to the actual defined type:

    num := testPB.Status_value[str]
    v := testPB.Status(num)
    

    Consider that if the str value doesn't exist in the map (note that it's case sensitive), the map look-up will return 0. Depending on how you defined your protobuffer, the 0 value might be mapped to a an enum instance that does not have "zero" semantics. This is why it is recommended to map 0 to an "unknown" instance:

    enum Status {
      UNKNOWN = 0;
      ENABLED = 1;
      // and so on
    }
    

    Which in Go correctly yields a makeshift zero-value if the string representation is effectively unknown:

    v := testPB.Status(testPB.Status_value["does_not_exist"]) 
    fmt.Println(v == testPB.Status_UNKNOWN) // true
    

    Go 1.18

    With generics, it is possible to write reusable code to construct protobuffer enums from string values:

    func Enum[T ~string, PB ~int32](val T, pbmap map[string]int32, dft PB) PB {
        v, ok := pbmap[string(val)]
        if !ok {
            return dft
        }
        return PB(v)
    }
    

    where:

    • the type parameter T is the string representation, which could also be a type with underlying type string, e.g. type MyEnum string
    • the argument pbmap is the <EnumName>_value from the generated protobuffer code
    • the type parameter PB is the protobuffer enum type.

    The function above takes a default value of type PB to (obviously) have a fallback in case the string representation is invalid, and also to allow type inference on PB, which otherwise would be used only as a return type.

    Usage:

    type SomeEnum string
    
    const (
        SomeEnumFoo SomeEnum = "FOO"
        SomeEnumBar SomeEnum = "BAR"
    ) 
    
    func main() {
        foo := SomeEnumFoo
        v := Enum(foo, pb.SomeEnum_value, pb.SomeEnum_Foo)
        //        ^ T  ^ map[string]int32 ^ default PB value
        // v is type pb.SomeEnum
    }