如何将descriptorpb.DescriptorProto转为protoreflect.MessageDescriptor?空指针报错
问题描述
尝试通过以下Go代码创建*dynamicpb.Message:
import ( "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/descriptorpb" "google.golang.org/protobuf/types/dynamicpb" "testing" ) func getDescriptorProto(fieldName string) protoreflect.MessageDescriptor { descProto := descriptorpb.DescriptorProto{ Field: []*descriptorpb.FieldDescriptorProto{ { Name: proto.String(fieldName), Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), }, }, } return descProto.ProtoReflect().Descriptor() } func getDynamicMessage(descriptor protoreflect.MessageDescriptor, value int32) *dynamicpb.Message { dynamicMessage := dynamicpb.NewMessage(descriptor) dynamicMessage.Set(dynamicMessage.Descriptor().Fields().ByName("id"), protoreflect.ValueOf(value)) return dynamicMessage } var ( idDescriptorProto = getDescriptorProto("id") ) var ( validIdMessage = getDynamicMessage(idDescriptorProto, int32(2)) )
运行时触发invalid memory address or nil pointer dereference错误,调试发现message info为nil,请问问题出在哪里?
问题原因与解决方案
核心问题
你错误地将descriptorpb.DescriptorProto自身的消息描述符当作自定义消息的描述符来使用:
descProto.ProtoReflect().Descriptor()返回的是**descriptorpb.DescriptorProto这个Protobuf元数据类型本身的描述符**,而不是你通过descProto定义的自定义消息的描述符。- 这种错误的描述符无法用于创建
dynamicpb.Message,后续调用Set时自然会触发空指针异常。
正确实现方式
要创建自定义消息的MessageDescriptor,必须将DescriptorProto包裹在FileDescriptorProto中,通过protodesc.NewFile构建合法的文件描述符,再从中提取消息描述符。修正后的代码如下:
import ( "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/descriptorpb" "google.golang.org/protobuf/types/dynamicpb" "google.golang.org/protobuf/types/protodesc" "testing" ) func getDescriptorProto(fieldName string) (protoreflect.MessageDescriptor, error) { // 构建文件描述符,包裹自定义消息的DescriptorProto fileDescProto := &descriptorpb.FileDescriptorProto{ Name: proto.String("test.proto"), MessageType: []*descriptorpb.DescriptorProto{ { Name: proto.String("TestMessage"), Field: []*descriptorpb.FieldDescriptorProto{ { Name: proto.String(fieldName), Number: proto.Int32(1), // 必须指定字段编号 Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(), }, }, }, }, } // 构建文件描述符对象 fileDesc, err := protodesc.NewFile(fileDescProto, nil) if err != nil { return nil, err } // 从文件描述符中获取自定义消息的描述符 return fileDesc.Messages().ByName("TestMessage"), nil } func getDynamicMessage(descriptor protoreflect.MessageDescriptor, value int32) *dynamicpb.Message { dynamicMessage := dynamicpb.NewMessage(descriptor) dynamicMessage.Set(dynamicMessage.Descriptor().Fields().ByName("id"), protoreflect.ValueOf(value)) return dynamicMessage } func TestDynamicMessage(t *testing.T) { idDescriptorProto, err := getDescriptorProto("id") assert.NoError(t, err) validIdMessage := getDynamicMessage(idDescriptorProto, int32(2)) assert.NotNil(t, validIdMessage) // 验证字段值是否正确设置 field := validIdMessage.Descriptor().Fields().ByName("id") assert.Equal(t, int32(2), validIdMessage.Get(field).Int()) }
关键修正点
- 添加文件描述符包裹:Protobuf的消息描述符必须属于某个文件,因此需要构建
FileDescriptorProto来承载自定义消息的DescriptorProto。 - 指定字段编号:字段必须有合法的编号(如示例中的
Number: proto.Int32(1)),否则无法通过描述符校验。 - 调整初始化时机:将全局变量初始化移到测试函数中,避免因错误的描述符导致程序启动时崩溃,同时方便处理错误。
内容的提问来源于stack exchange,提问作者Eagleye
相关产品推荐
相关产品推荐

