Skip to content

Instantly share code, notes, and snippets.

@StevenACoffman
Forked from IshanDaga/proto-to-map.go
Created August 29, 2025 01:12
Show Gist options
  • Save StevenACoffman/94642bd64afdc02aaefb019c575ea8d5 to your computer and use it in GitHub Desktop.
Save StevenACoffman/94642bd64afdc02aaefb019c575ea8d5 to your computer and use it in GitHub Desktop.
Create a go map from any proto message without proto->json->map
package main
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/dynamicpb"
)
func innerDynamicToMap(msg protoreflect.Message) (interface{}, error) {
mapData := make(map[string]interface{}, msg.Descriptor().Fields().Len())
for i := 0; i < msg.Descriptor().Fields().Len(); i++ {
field := msg.Descriptor().Fields().Get(i)
switch field.Cardinality() {
case protoreflect.Repeated:
list := msg.Get(field).List()
values := make([]interface{}, 0, list.Len())
for j := 0; j < list.Len(); j++ {
v, err := fieldToValue(field.Kind(), list.Get(j))
if err != nil {
return nil, err
}
values = append(values, v)
}
mapData[string(field.Name())] = values
default:
value, err := fieldToValue(field.Kind(), msg.Get(field))
if err != nil {
return nil, err
}
mapData[string(field.Name())] = value
}
}
return mapData, nil
}
func fieldToValue(kind protoreflect.Kind, value protoreflect.Value) (interface{}, error) {
switch kind {
case protoreflect.BoolKind:
return value.Bool(), nil
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind,
protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
return value.Int(), nil
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind,
protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
return value.Uint(), nil
case protoreflect.FloatKind, protoreflect.DoubleKind:
return value.Float(), nil
case protoreflect.StringKind:
return value.String(), nil
case protoreflect.BytesKind:
return value.Bytes(), nil
case protoreflect.EnumKind:
return value.Enum(), nil
case protoreflect.MessageKind:
return innerDynamicToMap(value.Message())
default:
return nil, fmt.Errorf("unsupported field type: %s", kind)
}
}
// for use with dynamicProto messages
// takes in a wire-buffer (encoded proto) and a dynamic message (created in runtime from a decriptor set)
// and returns a native go map
func DynamicToMap(in []byte, msg *dynamicpb.Message) (map[string]interface{}, error) {
err := proto.Unmarshal(in, msg)
if err != nil {
return nil, err
}
content, err := innerDynamicToMap(msg)
if err != nil {
return nil, err
}
if content, ok := content.(map[string]interface{}); ok {
return content, nil
}
return nil, fmt.Errorf("failed to convert dynamic message to map")
}
func main(){
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment